Ustawianie ścieżki

Aktualna ścieżka

Orientujemy się, gdzie jesteśmy funkcją getwd() (od get working directory)

getwd()
## [1] "C:/Users/Maluchnik/Documents/Statystyka1_R_"


Patrzymy co mamy w miejscu, w którym jesteśmy.

list.files()
##  [1] "ADR_MPZetc"                                 
##  [2] "dane"                                       
##  [3] "dodatki"                                    
##  [4] "grafika"                                    
##  [5] "kolokwia"                                   
##  [6] "Polski Sklep Internetowy - przykład analizy"
##  [7] "Projekty studentów z zeszłych lat"          
##  [8] "rsconnect"                                  
##  [9] "start"                                      
## [10] "styles.css"                                 
## [11] "zaj1.html"                                  
## [12] "zaj1.Rmd"                                   
## [13] "zaj2.html"                                  
## [14] "zaj2.Rmd"                                   
## [15] "zaj3.html"                                  
## [16] "zaj3.Rmd"


lub

dir()
##  [1] "ADR_MPZetc"                                 
##  [2] "dane"                                       
##  [3] "dodatki"                                    
##  [4] "grafika"                                    
##  [5] "kolokwia"                                   
##  [6] "Polski Sklep Internetowy - przykład analizy"
##  [7] "Projekty studentów z zeszłych lat"          
##  [8] "rsconnect"                                  
##  [9] "start"                                      
## [10] "styles.css"                                 
## [11] "zaj1.html"                                  
## [12] "zaj1.Rmd"                                   
## [13] "zaj2.html"                                  
## [14] "zaj2.Rmd"                                   
## [15] "zaj3.html"                                  
## [16] "zaj3.Rmd"



Zmiana ścieżki

Ścieżkę zmieniamy funkcją setwd() (od set working directory)

Skąd wziąć ścieżkę?

Można otworzyć eksplorator Windows i stamtąd skopiować interesującą nas ścieżkę, wklejając ją do polecenia (funkcji) setwd("...").

Kopiowanie ścieżki z Windows
Kopiowanie ścieżki z Windows


Problem takiego kopiowania!


\ (backslash - Windows) vs / (slash - reszta świata)


W R na Windowsie też piszemy / albo podwójny \\


Ciekawostka - alternatywa dla ukośników:

file.path("C:", "Users", "Maluchnik", "Documents")
## [1] "C:/Users/Maluchnik/Documents"



Klikanie 1



Klikanie 2

Alternatywny sposób ustawiania working directory poprzez wyklikanie prawego dolnego okna ->

Ustawianie ścieżki
Ustawianie ścieżki



W 2017 roku Jenny Bryan (RStudio/Posit, autorka m.in. pakietów readxl i googlesheets4) opisała na blogu tidyverse zasady project-oriented workflow. Najbardziej zapamiętany fragment to żartobliwa groźba (wklejona poniżej). Groziła podpaleniem komputerów w sytuacji użycia setwd(), bo to działa tylko lokalnie u użytkownika, bądź rm(list = ls()), bo ta komenda nie czyści sesji tak dokładnie, jak się wydaje (zostają wczytane pakiety i ustawione opcje). Zamiast tego: Session → Restart R.

Źródło: https://www.tidyverse.org/blog/2017/12/workflow-vs-script/


Rodzaje ścieżek

Ścieżka bezwzględna podaje pełny adres od korzenia systemu plików, np. C:/Users/Maluchnik/projekt/dane.csv (Windows) albo /home/Maluchnik/projekt/dane.csv (Linux/macOS). Działa niezależnie od tego, gdzie aktualnie „stoisz”, ale jest przywiązana do konkretnego komputera, więc kod przestaje działać u kogoś innego.

Ścieżka względna liczona jest od bieżącego katalogu roboczego (getwd()), np. dane/plik.csv.


Symbole: . oznacza bieżący folder, .. folder wyżej, ~ katalog domowy. Jest przenośna, ale jej znaczenie zmienia się, gdy zmienisz katalog roboczy.


Poruszanie się po katalogach

getwd()                         # gdzie jestem? (bieżący katalog roboczy)
setwd("dane")                   # wejdź głębiej, do podfolderu "dane"
getwd() 
setwd("..")                     # wyjdź poziom wyżej
getwd() 
setwd("dane/cwiczenia")         # kilka poziomów naraz
getwd() 
setwd("../..")                  # dwa poziomy wyżej
getwd() 
setwd("~")                      # katalog domowy użytkownika
getwd() 


Projekty RStudio

Pakiet {here} – lepsza alternatywa dla setwd()

setwd("C:/Users/Maluchnik/Documents/...") działa tylko na jednym komputerze. Gdy skrypt trafi do kolegi, promotora albo recenzenta, pierwsza linia kończy się błędem.

Rozwiązaniem są projekty RStudio (File → New Project…). Otwarcie pliku .Rproj automatycznie ustawia katalog roboczy na folder projektu, a wszystkie ścieżki piszemy już względnie: "dane/cities.txt".


Pakiet {here} idzie krok dalej: znajduje katalog główny projektu niezależnie od tego, z którego podfolderu uruchamiamy kod (np. z folderu skrypty/ albo z dokumentu R Markdown).

# install.packages("here")
library(here)
here()                      # katalog główny projektu
## [1] "C:/Users/Maluchnik/Documents/Statystyka1_R_"
here("dane", "cities.txt")  # pełna ścieżka, zbudowana poprawnie na każdym systemie
## [1] "C:/Users/Maluchnik/Documents/Statystyka1_R_/dane/cities.txt"


Druga dobra praktyka: Tools → Global Options → General i ustawić Save workspace to .RData on exit: Never oraz odznaczyć Restore .RData into workspace at startup. Każda sesja zaczyna się wtedy od czystej kartki, a wynik zależy wyłącznie od kodu, a nie od „czegoś, co zostało w pamięci z wczoraj”.



PRZYPOMNIENIE!

library() vs require()

library() przy braku pakietu zgłasza BŁĄD i zatrzymuje skrypt, require() tylko OSTRZEGA i zwraca FALSE - skrypt jedzie dalej i “wysypuje się” później, w mniej oczywistym miejscu. Na początku skryptu używamy więc library()




Wczytywanie danych

Ramki predefiniowane

Lista zbiorów danych ze wszystkich załadowanych pakietów (w tym z bazowego pakietu {datasets}) pojawi się po użyciu polecenia data().

data()


Same zbiory z {datasets}: data(package = "datasets").

data(package = "datasets")


Listę zbiorów ze wszystkich zainstalowanych pakietów (także tych niezaładowanych przez library()) wyświetlamy poleceniem: data(package = .packages(all.available = TRUE))

data(package = .packages(all.available = TRUE))


Z poprzednich zajęć znamy co najmniej jedną z poniższych ramek

head(iris, 3)
##   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
## 1          5.1         3.5          1.4         0.2  setosa
## 2          4.9         3.0          1.4         0.2  setosa
## 3          4.7         3.2          1.3         0.2  setosa
head(mtcars, 3)
##                mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4     21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710    22.8   4  108  93 3.85 2.320 18.61  1  1    4    1


Niektóre znajdują się w innych pakietach, zatem najpierw wczytujemy pakiet go zawierający

library(ggplot2)


A następnie

head(diamonds, 3) # -> 
## # A tibble: 3 × 10
##   carat cut     color clarity depth table price     x     y     z
##   <dbl> <ord>   <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1  0.23 Ideal   E     SI2      61.5    55   326  3.95  3.98  2.43
## 2  0.21 Premium E     SI1      59.8    61   326  3.89  3.84  2.31
## 3  0.23 Good    E     VS1      56.9    65   327  4.05  4.07  2.31

działa, choć w Global Environment zbioru nie widać.


Dlaczego? R szuka obiektów po kolei w “ścieżce wyszukiwania”:

search()                                                  # Global Environment jest pierwsze, potem pakiety
##  [1] ".GlobalEnv"        "package:ggplot2"   "package:here"     
##  [4] "package:stats"     "package:graphics"  "package:grDevices"
##  [7] "package:utils"     "package:datasets"  "package:methods"  
## [10] "Autoloads"         "package:base"
find("diamonds")                                          # zbiór znajduje się w pakiecie {ggplot2}
## [1] "package:ggplot2"
exists("diamonds", envir = globalenv(), inherits = FALSE) # FALSE - nie ma go w Global Environment
## [1] FALSE


data(diamonds) # -> Promise? Promise what?

data() tworzy w Global Environment “obietnicę” (promise) - obiekt zostanie faktycznie wczytany dopiero przy pierwszym użyciu (tzw. leniwe wczytywanie, lazy loading).


diamonds[1:5,1:5] # -> jest po pierwszej próbie użycia zbioru :D
## # A tibble: 5 × 5
##   carat cut     color clarity depth
##   <dbl> <ord>   <ord> <ord>   <dbl>
## 1  0.23 Ideal   E     SI2      61.5
## 2  0.21 Premium E     SI1      59.8
## 3  0.23 Good    E     VS1      56.9
## 4  0.29 Premium I     VS2      62.4
## 5  0.31 Good    J     SI2      63.3
exists("diamonds", 
       envir = globalenv(), 
       inherits = FALSE) # teraz TRUE
## [1] TRUE


Inny przykład …

dane_KolorOczuIWlosow = HairEyeColor # - dane studentów statystyki (kobiet i mężczyzn)
str(dane_KolorOczuIWlosow)
##  'table' num [1:4, 1:4, 1:2] 32 53 10 3 11 50 10 30 10 25 ...
##  - attr(*, "dimnames")=List of 3
##   ..$ Hair: chr [1:4] "Black" "Brown" "Red" "Blond"
##   ..$ Eye : chr [1:4] "Brown" "Blue" "Hazel" "Green"
##   ..$ Sex : chr [1:2] "Male" "Female"


Możemy też tak łopatologicznie.

iris <-  iris
mtcars <-  mtcars


# install.packages(c("languageR", "lme4", "psych", "palmerpenguins"))


Pakiet: languageR, zbiór: lexdec, zawiera: Czasy reakcji w zadaniu decydowania leksykalnego, uzyskane od 21 osób badanych dla 79 angielskich rzeczowników konkretnych, wraz ze zmiennymi powiązanymi z osobą badaną lub słowem.

# ?languageR::lexdec
head(languageR::lexdec[, c("Subject", "Word", "RT", "Correct", "NativeLanguage")], 3)
##   Subject   Word       RT Correct NativeLanguage
## 1      A1    owl 6.340359 correct        English
## 2      A1   mole 6.308098 correct        English
## 3      A1 cherry 6.349139 correct        English


Pakiet: lme4, zbiór: sleepstudy, zawiera: Czasy reakcji w kolejnych dniach deprywacji snu, 18 osób

# ?lme4::sleepstudy
head(lme4::sleepstudy, 3)
##   Reaction Days Subject
## 1 249.5600    0     308
## 2 258.7047    1     308
## 3 250.8006    2     308


Pakiet: bfi, zbiór: psych, zawiera: kwestionariusz Wielkiej Piątki (25 pozycji), ok. 2800 osób, z brakami danych

# ?psych::bfi
dim(psych::bfi)
## [1] 2800   28


Pakiet: palmerpenguins, zbiór: penguins, zawiera: pomiary pingwinów z Antarktydy, z brakami danych

https://education.rstudio.com/blog/2020/07/palmerpenguins-cran/

# ?palmerpenguins::penguins
head(palmerpenguins::penguins, 3)
## # A tibble: 3 × 8
##   species island    bill_length_mm bill_depth_mm flipper_length_mm body_mass_g
##   <fct>   <fct>              <dbl>         <dbl>             <int>       <int>
## 1 Adelie  Torgersen           39.1          18.7               181        3750
## 2 Adelie  Torgersen           39.5          17.4               186        3800
## 3 Adelie  Torgersen           40.3          18                 195        3250
## # ℹ 2 more variables: sex <fct>, year <int>


European Social Survey (https://www.europeansocialsurvey.org – pliki m.in. w formacie SPSS, z polską próbą).


Zbiór iris opublikował Ronald Fisher w 1936 roku w czasopiśmie Annals of Eugenics (dane zebrał botanik Edgar Anderson). Fisher był jednym z twórców nowoczesnej statystyki i jednocześnie zwolennikiem eugeniki. To jeden z powodów, dla których część środowiska R zaczęła używać palmerpenguins zamiast iris.

Czy kontekst powstania danych powinien wpływać na to, czy ich używamy? Czy zmiana zbioru w podręcznikach coś zmienia? Jakie inne „klasyczne” dane lub narzędzia w psychologii mają kontrowersyjną historię?


Dodatkowe źródło informacji


Dokumentacja wszystkich pakietów. Pozwala łatwo przeszukiwać dokumentację każdej wersji każdego pakietu R dostępnego w repozytoriach CRAN i Bioconductor, a obecnie obejmuje ponad 33 tysiące pakietów. To w praktyce te same strony pomocy, które widzisz w R po wpisaniu ?funkcja, tylko dostępne w przeglądarce, z ładniejszym formatowaniem i bez konieczności instalowania pakietu.





.txt

Najpopularniejszy format danych wczytywanych do R to dane tekstowe. Jest to format prosty i uniwersalny – cała informacja zakodowana jest w znakach widocznych z poziomu Notatnika, a pliki da się odczytać w różnych programach na każdym systemie operacyjnym.

Klasyfikacji plików tekstowych możemy dokonać na podstawie sposobu organizacji kolumn. Mamy zatem:

  • pliki rozdzielane separatorem (średnik, przecinek, tabulator, znak pionowej linii, etc.)

  • pliki o stałej szerokości kolumn (fixed width columns)


Zawsze, używając po raz pierwszy jakiejś funkcji, przejrzyjmy wcześniej jej dokumentację: ?read.table (lub help("read.table")).

Dokumentację całego pakietu, w którym ta funkcja się znajduje, otworzymy przez help(package = "utils").



read.table to uogólniona funkcja do wczytywania wielu typów plików tekstowych.

miasta <- read.table("dane/cities.txt",  # nazwa pliku wraz z rozszerzeniem 
                     header = TRUE,      # pierwszy WIERSZ jako nagłówek?
                     sep = ",",          # separator kolumn
                     dec = ".")          # znak oddzielający część całkowitą od dziesiętnej


Sprawdzenie danych (i utrwalenie podstawowych funkcji rozruchowych – do rozeznania się w ramce bez niepotrzebnego print’owania w konsoli)

is.data.frame(miasta) # czy to data.frame?
## [1] TRUE
head(miasta,3) # wyrzucenie do konsoli pierwszych 3 wierszy
##   Country             City       AccentCity Region Population Latitude
## 1      ad andorra la vella Andorra la Vella     07      20430 42.50000
## 2      ad          canillo          Canillo     02       3292 42.56667
## 3      ad           encamp           Encamp     03      11224 42.53333
##   Longitude
## 1  1.516667
## 2  1.600000
## 3  1.583333
tail(miasta,3) # wyrzucenie do konsoli ostatnich 3 wierszy
##       Country           City     AccentCity Region Population  Latitude
## 47978      zw       shurugwi       Shurugwi     07      17107 -19.66667
## 47979      zw victoria falls Victoria Falls     00      36702 -17.93333
## 47980      zw     zvishavane     Zvishavane     07      79876 -20.33333
##       Longitude
## 47978  30.00000
## 47979  25.83333
## 47980  30.03333
is.numeric(miasta$Population) # upewnienie się co do typu danych konkretnej zmiennej
## [1] TRUE
str(miasta) # struktura danych
## 'data.frame':    47980 obs. of  7 variables:
##  $ Country   : chr  "ad" "ad" "ad" "ad" ...
##  $ City      : chr  "andorra la vella" "canillo" "encamp" "la massana" ...
##  $ AccentCity: chr  "Andorra la Vella" "Canillo" "Encamp" "La Massana" ...
##  $ Region    : chr  "07" "02" "03" "04" ...
##  $ Population: int  20430 3292 11224 7211 15854 2553 8020 603687 1137376 543942 ...
##  $ Latitude  : num  42.5 42.6 42.5 42.5 42.5 ...
##  $ Longitude : num  1.52 1.6 1.58 1.52 1.53 ...
summary(miasta) # podsumowanie zmiennych
##       Country             City           AccentCity          Region     
##  Length   :47980   Length   :47980   Length   :47980   Length   :47980  
##  N.unique :  209   N.unique :43183   N.unique :43347   N.unique :  369  
##  N.blank  :    0   N.blank  :    0   N.blank  :    0   N.blank  :    0  
##  Min.nchar:    2   Min.nchar:    1   Min.nchar:    1   Min.nchar:    2  
##  Max.nchar:    2   Max.nchar:   30   Max.nchar:   30   Max.nchar:    2  
##                                                                         
##    Population          Latitude        Longitude      
##  Min.   :       7   Min.   :-54.80   Min.   :-179.50  
##  1st Qu.:    3732   1st Qu.: 14.65   1st Qu.: -41.68  
##  Median :   10779   Median : 39.13   Median :  19.90  
##  Mean   :   47720   Mean   : 30.62   Mean   :  14.67  
##  3rd Qu.:   27991   3rd Qu.: 47.87   3rd Qu.:  58.41  
##  Max.   :31480498   Max.   : 78.93   Max.   : 179.32


Czy moglibyśmy opuścić argumenty funkcji read.table?

miasta2 <- read.table("dane/cities.txt") 


Nie działa, bo domyślnie argumenty mają następujące wartości: header = FALSE, sep = "", dec = "." (sep = "" oznacza „dowolny biały znak”, czyli spacje i tabulatory).





Pliki o stałej szerokości kolumn


Wspomnieliśmy o nich wyżej, ale nie pokazaliśmy przykładu. Takie pliki spotkamy w starszych systemach, w danych urzędowych i w eksportach ze starych programów statystycznych. Kolumn nie oddziela tu żaden znak – o tym, gdzie kończy się zmienna, decyduje liczba znaków.

Zasymulujmy sobie taką ramkę

# tworzymy mały plik tymczasowy, żeby nie zależeć od plików na dysku
plik_fwf <- tempfile(fileext = ".txt")
writeLines(c("PLWarszawa  1863",
             "PLGdansk    0486",
             "CZPraha     1357"), plik_fwf)

readLines(plik_fwf)  # podgląd "surowego" pliku
## [1] "PLWarszawa  1863" "PLGdansk    0486" "CZPraha     1357"
read.fwf(plik_fwf,
         widths = c(2, 10, 4),                     # szerokości kolumn w znakach
         col.names = c("kraj", "miasto", "ludnosc_tys"))
##   kraj     miasto ludnosc_tys
## 1   PL Warszawa          1863
## 2   PL Gdansk             486
## 3   CZ Praha             1357

Odpowiednik z pakietu readr to read_fwf() z pomocniczą funkcją fwf_widths().





.csv

Zajrzyjmy do dokumentacji: ?read.csv.

Jest to funkcja z rodziny read.table, służąca do wczytywania danych z pliku csv (tekstowego, rozdzielanego przecinkami).

daneusa <- read.csv("dane/daneusa.csv") # wczytanie

Domyślne założenia funkcji:

  • separator → sep = ","

  • znak dziesiętny → dec = "."

  • header = TRUE → plik zawiera nazwy kolumn w pierwszym wierszu (nagłówek)


Jeśli jest inaczej, można użyć odpowiednich dodatkowych argumentów funkcji:

daneusa <- read.csv("dane/daneusa.csv", 
                    sep = ",",
                    dec = ".",
                    header = TRUE)


Jest też funkcja read.csv2() (stworzona dla Europy kontynentalnej), która ma inne domyślne parametry: sep = ";" oraz dec = ",".

Co by się stało, gdybyśmy skorzystali z niej do wczytania tego pliku?

daneusa <- read.csv2("dane/daneusa.csv")

Warto zwrócić na to uwagę w Global Environment. Inny separator kolumn spowodował, że R nie rozróżnia kolumn – cały wiersz trafił do jednej kolumny.


Skąd w ogóle read.csv2? W polskich (i wielu europejskich) ustawieniach regionalnych przecinek jest separatorem dziesiętnym (3,14). Gdyby kolumny też oddzielał przecinek, liczba 3,14 rozpadłaby się na dwie kolumny. Dlatego polski Excel zapisuje pliki „CSV” ze średnikami, mimo że nazwa formatu to comma-separated values. Format CSV nie ma zresztą jednej, obowiązującej normy, więc „plik CSV” może w praktyce oznaczać bardzo różne rzeczy.


Wróćmy więc do poprzedniej funkcji i wczytajmy ponownie (poprawnie) dane:

daneusa <- read.csv("dane/daneusa.csv")
class(daneusa); str(daneusa)
## [1] "data.frame"
## 'data.frame':    35072 obs. of  29 variables:
##  $ UMARSTAT: chr  "Never married" "Separated" "Married_live together" "Divorced" ...
##  $ UCUREMP : chr  "No" "Yes" "No" "No" ...
##  $ UCURNINS: chr  "Yes" "No" "No" "Yes" ...
##  $ USATMED : chr  "Very satisfied" "Very satisfied" "Very satisfied" "Little dissatisfied" ...
##  $ URELATE : int  2 2 5 4 0 1 0 1 0 0 ...
##  $ REGION  : chr  "Midwest" "Midwest" "Midwest" "Midwest" ...
##  $ STATE   : chr  "WI" "WI" "WI" "WI" ...
##  $ HHID    : int  55616128 54704000 57874272 54106816 54569152 50199136 55660032 52425472 51796480 51796480 ...
##  $ FHOSP   : chr  "No" "No" "No" "No" ...
##  $ FDENT   : int  0 2 0 0 2 0 0 1 5 3 ...
##  $ FEMER   : int  0 0 1 0 0 1 1 5 5 4 ...
##  $ FDOCT   : int  0 0 0 1 0 1 3 1 0 0 ...
##  $ UIMMSTAT: chr  "US-born citizen" "US-born citizen" "US-born citizen" "US-born citizen" ...
##  $ U_USBORN: chr  "Yes" "Yes" "Yes" "Yes" ...
##  $ UAGE    : int  22 30 33 41 34 39 19 18 24 21 ...
##  $ U_FTPT  : chr  "Full-time" "Full-time" "Part-time" "Part-time" ...
##  $ U_WKSLY : num  52 52 52 43 52 39 30 48 52 26 ...
##  $ U_HRSLY : int  40 40 30 40 40 45 20 32 40 40 ...
##  $ U_USHRS : int  40 40 30 25 40 30 40 40 40 47 ...
##  $ HEARNVAL: int  0 31468 24700 60000 55280 41333 105000 0 6000 42000 ...
##  $ HOTHVAL : int  0 5950 11340 39002 4200 8177 1000 44400 4800 1272 ...
##  $ HRETVAL : int  0 0 0 0 0 0 0 12000 0 0 ...
##  $ HSSVAL  : int  0 0 4920 0 0 5933 0 20400 0 0 ...
##  $ HWSVAL  : int  0 31468 24700 60000 55280 41333 105000 0 0 42000 ...
##  $ UBRACE  : chr  "White" "White" "White" "Black" ...
##  $ GENDER  : chr  "Female" "Female" "Male" "Female" ...
##  $ UEDUC3  : chr  "No HS diploma or GED" "HS diploma or GED, no bachelor's degree" "No HS diploma or GED" "No HS diploma or GED" ...
##  $ CEYES   : chr  "hazel" "blue" "brown" "brown" ...
##  $ CHAIR   : chr  "brown" "black" "brown" "black" ...


Dawniej wszystkie kolumny tekstowe były domyślnie konwertowane do factor. Aby tego uniknąć, trzeba było jawnie użyć opcji stringsAsFactors = FALSE. Od wersji R 4.0.0 (kwiecień 2020) domyślną wartością jest FALSE.

daneusa <- read.csv("dane/daneusa.csv",
                    stringsAsFactors = FALSE)

Domyślne stringsAsFactors = TRUE przetrwało w R ponad 20 lat i było źródłem niezliczonych błędów: liczby wczytane jako tekst zamieniały się w czynniki, a as.numeric() zwracało wtedy numery poziomów zamiast wartości. Roger Peng opisał tę historię we wpisie stringsAsFactors: An unauthorized biography (2015). Ustawienie miało sens w czasach, gdy R służył głównie do modeli statystycznych, a czynniki oszczędzały pamięć. Gdy zmieniono je w R 4.0.0, część użytkowników świętowała to jak koniec epoki.

Państwo z nowym R nie zobaczą już tego problemu, ale spotkają go w starych skryptach i odpowiedziach na StackOverflow (wiemy co to za strona?).


Inne warianty funkcji read.table() to np. read.delim() oraz read.delim2() – do plików rozdzielanych tabulatorem (sep = "\t"; wersja z „2” ma dodatkowo dec = ",").



Dane można też importować poprzez „Import Dataset” w oknie środowiska.

“Przycisk do manualnego wczytania danych”
“Przycisk do manualnego wczytania danych”


Dobra praktyka
Po wyklikaniu importu skopiuj kod z okienka Code Preview do skryptu. Wtedy import da się powtórzyć bez klikania, a za pół roku będzie wiadomo, jak dane zostały wczytane.





Kodowanie znaków – „krzaki” zamiast polskich liter

Plik tekstowy to w rzeczywistości ciąg bajtów. Kodowanie (encoding) to umowa, który bajt oznacza którą literę. Litery bez ogonków zapisywane są wszędzie tak samo, ale ą, ł czy ż w różnych kodowaniach mają różne bajty. Najczęściej spotkamy:

  • UTF-8 – dzisiejszy standard (od R 4.2 także domyślny w R na Windowsie),

  • Windows-1250 (CP1250) – starsze polskie pliki z Windowsa i Excela,

  • ISO-8859-2 (Latin-2) – starsze pliki spoza Windowsa.


# tworzymy plik zapisany w kodowaniu Windows-1250
plik_1250 <- tempfile(fileext = ".csv")
pol <- file(plik_1250, open = "w", encoding = "windows-1250")
writeLines(c("imie,miasto,wynik",
             "Michał,Łódź,12",
             "Zażółć,Gęśla Jaźń,15"), pol)
close(pol)

# podgląd surowych linii - zamiast polskich liter widać "krzaki" (kody bajtów)
readLines(plik_1250)
## [1] "imie,miasto,wynik"                           
## [2] "Micha\xb3,\xa3\xf3d\x9f,12"                  
## [3] "Za\xbf\xf3\xb3\xe6,G\xea\x9cla Ja\x9f\xf1,15"
# wczytanie bez podania kodowania: w UTF-8 (R >= 4.2 na Windowsie, macOS, Linux)
# kończy się błędem "invalid multibyte string"; w starszym R - "krzakami" w danych.
# try() pozwala zobaczyć komunikat błędu bez zatrzymywania całego skryptu
try(read.csv(plik_1250))
## Error in type.convert.default(data[[i]], as.is = as.is[i], dec = dec,  : 
##   invalid multibyte string at '<a3><f3>d<9f>'
# poprawnie - bazowe R
read.csv(plik_1250, fileEncoding = "windows-1250")
##     imie     miasto wynik
## 1 Michał       Łódź    12
## 2 Zażółć Gęśla Jaźń    15
# poprawnie - readr
readr::read_csv(plik_1250,
                locale = readr::locale(encoding = "windows-1250"),
                show_col_types = FALSE)
## # A tibble: 2 × 3
##   imie   miasto     wynik
##   <chr>  <chr>      <dbl>
## 1 Michał Łódź          12
## 2 Zażółć Gęśla Jaźń    15
# a gdy nie wiemy, w jakim kodowaniu jest plik:
readr::guess_encoding(plik_1250)
## # A tibble: 1 × 2
##   encoding     confidence
##   <chr>             <dbl>
## 1 windows-1250       0.31


Zapisując własne pliki, najlepiej zawsze używać UTF-8 (write.csv(..., fileEncoding = "UTF-8"); funkcje readr::write_csv() robią to domyślnie).


W polskich realiach to najczęstszy problem przy wczytywaniu danych. Komunikat invalid multibyte string w zobaczą Państwo jeszcze nie raz.





Braki danych przy wczytywaniu

W badaniach psychologicznych czy socjologicznych braki danych rzadko są zapisane jako puste pole. Częściej spotkamy -99, 999, "brak", "NA", "n/a" albo "." (to ostatnie to tradycja SAS-a i SPSS-a). Jeśli R o tym nie wie, -99 zostanie potraktowane jak prawdziwa wartość i zaniży średnią – bez żadnego ostrzeżenia.

plik_braki <- tempfile(fileext = ".csv")
writeLines(c("id;wiek;wynik",
             "1;23;-99",
             "2;brak;14",
             "3;31;",
             "4;27;18"), plik_braki)

(zle <- read.csv2(plik_braki))
##   id wiek wynik
## 1  1   23   -99
## 2  2 brak    14
## 3  3   31    NA
## 4  4   27    18
str(zle)            # wiek wczytany jako tekst, a -99 jako zwykła liczba
## 'data.frame':    4 obs. of  3 variables:
##  $ id   : int  1 2 3 4
##  $ wiek : chr  "23" "brak" "31" "27"
##  $ wynik: int  -99 14 NA 18
mean(zle$wynik, na.rm = TRUE)   # średnia zaniżona przez -99
## [1] -22.33333
(dobrze <- read.csv2(plik_braki, na.strings = c("-99", "brak", "")))
##   id wiek wynik
## 1  1   23    NA
## 2  2   NA    14
## 3  3   31    NA
## 4  4   27    18
str(dobrze)
## 'data.frame':    4 obs. of  3 variables:
##  $ id   : int  1 2 3 4
##  $ wiek : int  23 NA 31 27
##  $ wynik: int  NA 14 NA 18
mean(dobrze$wynik, na.rm = TRUE)
## [1] 16
# to samo w readr:
readr::read_csv2(plik_braki, na = c("-99", "brak", ""), show_col_types = FALSE)
## # A tibble: 4 × 3
##      id  wiek wynik
##   <dbl> <dbl> <dbl>
## 1     1    23    NA
## 2     2    NA    14
## 3     3    31    NA
## 4     4    27    18


Zasada

Zanim policzysz pierwszą statystykę, sprawdź summary(). Minimum równe -99 albo maksimum równe 999 w zmiennej „wiek” to sygnał alarmowy.




readr

Jest to pakiet z uniwersum tidyverse (https://www.tidyverse.org/). Na stronie pakietu czytamy, że the tidyverse is an opinionated collection of R packages designed for data science. All packages share an underlying design philosophy, grammar, and data structures.

Sprawdźmy, jak to działa. Ważna informacja jest taka, że funkcje wczytujące z readr działają zwykle szybciej niż ich odpowiedniki dostępne domyślnie w pakiecie utils (od wersji 2.0 readr korzysta z silnika vroom, który czyta dane wielowątkowo i „leniwie”). Zwracają też tibble zamiast zwykłej ramki i wyraźnie informują, jakie typy kolumn odgadły.

# ?read_csv2
library(readr)

read_csv2(file = "dane/dane_titanic.csv", 
          n_max = 5) # inaczej niż w przypadku poznanej właśnie funkcji read.csv2()
## # A tibble: 5 × 1
##   passenger_id,pclass,survived,name,sex,age,sibsp,parch,ticket,fare,cabin,emba…¹
##   <chr>                                                                         
## 1 "1,1,1,\"Allen, Miss. Elisabeth Walton\",1,29,0,0,24160,211.3375,B5,S,\"St Lo…
## 2 "2,1,1,\"Allison, Master. Hudson Trevor\",0,0.9167,1,2,113781,151.55,C22 C26,…
## 3 "3,1,0,\"Allison, Miss. Helen Loraine\",1,2,1,2,113781,151.55,C22 C26,S,\"Mon…
## 4 "4,1,0,\"Allison, Mr. Hudson Joshua Creighton\",0,30,1,2,113781,151.55,C22 C2…
## 5 "5,1,0,\"Allison, Mrs. Hudson J C (Bessie Waldo Daniels)\",1,25,1,2,113781,15…
## # ℹ abbreviated name:
## #   ¹​`passenger_id,pclass,survived,name,sex,age,sibsp,parch,ticket,fare,cabin,embarked,home.dest`
# Przeczytajmy komunikat! readr informuje, że użył ";" jako separatora
# i że wszystko trafiło do JEDNEJ kolumny - to sygnał, że wybraliśmy złą funkcję.

read_csv(file = "dane/dane_titanic.csv", 
         n_max = 5) # tu działa
## # A tibble: 5 × 13
##   passenger_id pclass survived name    sex    age sibsp parch ticket  fare cabin
##          <dbl>  <dbl>    <dbl> <chr> <dbl>  <dbl> <dbl> <dbl>  <dbl> <dbl> <chr>
## 1            1      1        1 Alle…     1 29         0     0  24160  211. B5   
## 2            2      1        1 Alli…     0  0.917     1     2 113781  152. C22 …
## 3            3      1        0 Alli…     1  2         1     2 113781  152. C22 …
## 4            4      1        0 Alli…     0 30         1     2 113781  152. C22 …
## 5            5      1        0 Alli…     1 25         1     2 113781  152. C22 …
## # ℹ 2 more variables: embarked <chr>, home.dest <chr>
read_delim(file = "dane/dane_titanic.csv", 
           delim = ",", # -> działa po uzupełnieniu dodatkowego argumentu
           n_max = 5)
## # A tibble: 5 × 13
##   passenger_id pclass survived name    sex    age sibsp parch ticket  fare cabin
##          <dbl>  <dbl>    <dbl> <chr> <dbl>  <dbl> <dbl> <dbl>  <dbl> <dbl> <chr>
## 1            1      1        1 Alle…     1 29         0     0  24160  211. B5   
## 2            2      1        1 Alli…     0  0.917     1     2 113781  152. C22 …
## 3            3      1        0 Alli…     1  2         1     2 113781  152. C22 …
## 4            4      1        0 Alli…     0 30         1     2 113781  152. C22 …
## 5            5      1        0 Alli…     1 25         1     2 113781  152. C22 …
## # ℹ 2 more variables: embarked <chr>, home.dest <chr>




Kontrola typów kolumn i wybór kolumn

Komunikat „Column specification” nie jest przeszkodą, tylko informacją. Możemy go wyciszyć (show_col_types = FALSE), ale lepiej typy zadeklarować samemu – wtedy plik z błędem (np. tekstem w kolumnie liczbowej) nie przejdzie niezauważony.

titanic_r <- read_csv(
  file = "dane/dane_titanic.csv",
  col_select = c(name, pclass, sex, age, fare),  # wczytujemy tylko potrzebne kolumny
  col_types = cols(
    pclass = col_factor(levels = c("1", "2", "3")),
    sex    = col_integer(),
    age    = col_double(),
    .default = col_guess()                       # pozostałe niech readr odgadnie
  )
)
titanic_r
## # A tibble: 1,309 × 5
##    name                                            pclass   sex    age  fare
##    <chr>                                           <fct>  <int>  <dbl> <dbl>
##  1 Allen, Miss. Elisabeth Walton                   1          1 29     211. 
##  2 Allison, Master. Hudson Trevor                  1          0  0.917 152. 
##  3 Allison, Miss. Helen Loraine                    1          1  2     152. 
##  4 Allison, Mr. Hudson Joshua Creighton            1          0 30     152. 
##  5 Allison, Mrs. Hudson J C (Bessie Waldo Daniels) 1          1 25     152. 
##  6 Anderson, Mr. Harry                             1          0 48      26.6
##  7 Andrews, Miss. Kornelia Theodosia               1          1 63      78.0
##  8 Andrews, Mr. Thomas Jr                          1          0 39       0  
##  9 Appleton, Mrs. Edward Dale (Charlotte Lamson)   1          1 53      51.5
## 10 Artagaveytia, Mr. Ramon                         1          0 71      49.5
## # ℹ 1,299 more rows
problems(titanic_r)  # wiersze, których nie udało się wczytać zgodnie z typem (pusta tabela = OK)
## # A tibble: 0 × 5
## # ℹ 5 variables: row <int>, col <int>, expected <chr>, actual <chr>, file <chr>




data.table

Tak jak świat gitarzystów podzielony jest na sięgających po Fendery bądź GIBSONy, a czytelnicy komiksów o (przeważnie amerykańskich) superbohaterach na fanów DC bądź MARVEL’a, tak świat R’owców dzieli się na ludzi ze stajni {tidyverse} i tych używających {data.table}. Performance data.table jest niesamowity, jeśli chodzi o szybkość, jednak jest wielu przeciwników tego rozwiązania (składnia data wranglingu bardzo się różni).


# install.packages("data.table")
library(data.table)
fread(input = "dane/dane_titanic.csv", 
      nrows = 5)
##    passenger_id pclass survived                                            name
##           <int>  <int>    <int>                                          <char>
## 1:            1      1        1                   Allen, Miss. Elisabeth Walton
## 2:            2      1        1                  Allison, Master. Hudson Trevor
## 3:            3      1        0                    Allison, Miss. Helen Loraine
## 4:            4      1        0            Allison, Mr. Hudson Joshua Creighton
## 5:            5      1        0 Allison, Mrs. Hudson J C (Bessie Waldo Daniels)
##      sex     age sibsp parch ticket     fare   cabin embarked
##    <int>   <num> <int> <int>  <int>    <num>  <char>   <char>
## 1:     1 29.0000     0     0  24160 211.3375      B5        S
## 2:     0  0.9167     1     2 113781 151.5500 C22 C26        S
## 3:     1  2.0000     1     2 113781 151.5500 C22 C26        S
## 4:     0 30.0000     1     2 113781 151.5500 C22 C26        S
## 5:     1 25.0000     1     2 113781 151.5500 C22 C26        S
##                          home.dest
##                             <char>
## 1:                    St Louis, MO
## 2: Montreal, PQ / Chesterville, ON
## 3: Montreal, PQ / Chesterville, ON
## 4: Montreal, PQ / Chesterville, ON
## 5: Montreal, PQ / Chesterville, ON




data.table vs dplyr


Operacja / Warunek Zwycięzca wydajności Szczegóły
Bardzo duże zbiory danych data.table Zwykle wyraźnie szybszy, głównie dzięki modyfikowaniu danych „w miejscu” (przez referencję), bez kopiowania całego zbioru.
Grupowanie i agregacja
(by vs. group_by/summarise)
data.table Wykorzystuje szybkie sortowanie pozycyjne (radix sort), co daje przewagę zwłaszcza przy dużej liczbie grup.
Czytanie danych
(fread vs. read_csv)
data.table fread() jest jedną z najszybszych metod wczytywania plików CSV w R; konkurencję stanowią dziś m.in. readr 2.x (silnik vroom), arrow i duckdb.
Mniejsze zbiory danych
(< 1 mln wierszy)
Remis Różnice liczone są w milisekundach. Ważniejsze od szybkości stają się czytelność kodu i to, którą składnię zna zespół.


Filmik sprzed kilku lat, ciekawe w sumie czy wciąż aktualny:

https://www.youtube.com/watch?v=SfrjF5YSj0Y


Aktualne porównania wydajności wielu narzędzi (R, Python, bazy danych) publikuje DuckDB Labs db-benchmark: https://duckdblabs.github.io/db-benchmark/.

Pomost między światami: pakiet dtplyr pozwala pisać w składni dplyr, a pod spodem wykonuje obliczenia przez data.table.




Ćwiczenie

„Zepsute pliki”

Poniższy kod tworzy w folderze dane/cwiczenia/zepsute/ sześć plików. Każdy zawiera inną pułapkę.

Zadanie

Jak wczytać każdy plik poprawnie i udowodnić to wynikiem str() i summary() (właściwe typy kolumn, poprawne polskie znaki, braki danych jako NA, sensowne nazwy kolumn). Można zrobić z tego wyścig – która para pierwsza wczyta wszystkie sześć?

Wskazówka

Zanim napiszemy kod, zawsze warto podejrzeć każdy plik – w Notatniku albo poleceniem readLines("ścieżka", n = 5).

# --- GENERATOR PLIKÓW DO ĆWICZENIA ---
set.seed(2026)
folder <- "dane/cwiczenia/zepsute"
dir.create(folder, recursive = TRUE, showWarnings = FALSE)
n <- 30
baza <- data.frame(
  id     = sprintf("P%02d", 1:n),
  wiek   = sample(19:35, n, replace = TRUE),
  plec   = sample(c("kobieta", "mężczyzna", "inna"), n, replace = TRUE, prob = c(.55, .4, .05)),
  miasto = sample(c("Łódź", "Gdańsk", "Kraków", "Wrocław", "Poznań", "Białystok"), n, replace = TRUE),
  stres  = round(rnorm(n, 3.2, 0.8), 2),
  rt_ms  = round(rnorm(n, 650, 90))
)

baza
##     id wiek      plec    miasto stres rt_ms
## 1  P01   19   kobieta      Łódź  3.81   656
## 2  P02   24   kobieta Białystok  2.30   595
## 3  P03   31      inna   Wrocław  3.80   680
## 4  P04   33 mężczyzna      Łódź  1.96   593
## 5  P05   30      inna      Łódź  3.12   554
## 6  P06   22 mężczyzna   Wrocław  3.02   630
## 7  P07   34   kobieta Białystok  3.49   535
## 8  P08   23 mężczyzna    Poznań  2.14   773
## 9  P09   30 mężczyzna   Wrocław  1.09   604
## 10 P10   20   kobieta    Poznań  3.22   662
## 11 P11   33      inna    Kraków  2.86   572
## 12 P12   32   kobieta Białystok  2.74   680
## 13 P13   35   kobieta Białystok  3.49   811
## 14 P14   30 mężczyzna      Łódź  2.05   710
## 15 P15   28 mężczyzna   Wrocław  3.48   818
## 16 P16   20   kobieta    Kraków  1.93   667
## 17 P17   23   kobieta      Łódź  1.87   556
## 18 P18   30   kobieta      Łódź  3.27   520
## 19 P19   23 mężczyzna   Wrocław  3.71   663
## 20 P20   34   kobieta Białystok  3.61   553
## 21 P21   21 mężczyzna    Poznań  1.66   615
## 22 P22   30 mężczyzna      Łódź  3.21   680
## 23 P23   21 mężczyzna    Kraków  2.23   709
## 24 P24   21   kobieta   Wrocław  2.95   751
## 25 P25   28 mężczyzna      Łódź  3.10   549
## 26 P26   22 mężczyzna    Poznań  2.17   569
## 27 P27   26   kobieta    Poznań  3.68   575
## 28 P28   20 mężczyzna   Wrocław  3.83   649
## 29 P29   31 mężczyzna    Poznań  3.34   703
## 30 P30   27   kobieta    Poznań  4.44   518
# --- 
# 1. średnik + przecinek dziesiętny ("polski Excel")
write.csv2(baza, file.path(folder, "01_ankieta_pl.csv"), 
           row.names = FALSE, 
           fileEncoding = "UTF-8")

# --- 
# 2. kodowanie Windows-1250
write.csv(baza, file.path(folder, 
                          "02_kodowanie.csv"), 
          row.names = FALSE, 
          fileEncoding = "windows-1250")

# --- 
# 3. tabulator + braki zakodowane jako -99 i "brak"
b3 <- baza
b3$stres[c(3, 11, 20)] <- -99
b3$wiek <- as.character(b3$wiek); b3$wiek[c(5, 17)] <- "brak"
write.table(b3, 
            file.path(folder, 
                      "03_braki.tsv"), 
            sep = "\t", 
            row.names = FALSE, 
            quote = FALSE, 
            fileEncoding = "UTF-8")

# --- 
# 4. eksport w stylu Qualtrics: 3 wiersze nagłówka (nazwy, treść pytań, identyfikatory)
naglowki <- c(paste(names(baza), 
                    collapse = ","),
              "Identyfikator,Ile masz lat?,Płeć,Miejsce zamieszkania,Poziom stresu (1-5),Czas reakcji [ms]",
              '{"ImportId":"id"},{"ImportId":"QID1"},{"ImportId":"QID2"},{"ImportId":"QID3"},{"ImportId":"QID4"},{"ImportId":"QID5"}')

plik4 <- file(file.path(folder, "04_qualtrics.csv"), open = "w", encoding = "UTF-8")
writeLines(naglowki, plik4)
write.table(baza, plik4, sep = ",", row.names = FALSE, col.names = FALSE, quote = FALSE)
close(plik4)

# --- 
# 5. stała szerokość kolumn (bez polskich znaków - patrz uwaga w rozwiązaniu)
linie <- sprintf("%-4s%2d%7.2f%5d", baza$id, baza$wiek, baza$stres, baza$rt_ms)
writeLines(linie, file.path(folder, "05_stala_szerokosc.txt"))

# --- 
# 6. Excel: dane dopiero w drugim arkuszu, w pierwszym jest opis badania
if (requireNamespace("writexl", quietly = TRUE)) {
  writexl::write_xlsx(
    list(opis = data.frame(informacja = c("Badanie pilotażowe – stres i czas reakcji",
                                          "Dane w arkuszu 'dane'",
                                          "Kontakt: prowadzący zajęcia")),
         dane = baza),
    path = file.path(folder, "06_excel_dwa_arkusze.xlsx"))
}
list.files(folder)
## [1] "01_ankieta_pl.csv"         "02_kodowanie.csv"         
## [3] "03_braki.tsv"              "04_qualtrics.csv"         
## [5] "05_stala_szerokosc.txt"    "06_excel_dwa_arkusze.xlsx"


Qualtrics to komercyjna platforma do tworzenia i prowadzenia ankiet internetowych. Jest bardzo popularna w nauce, zwłaszcza w psychologii i naukach społecznych, a także w badaniach rynku i firmach. Wiele uczelni ma na nią licencję i udostępnia ją pracownikom i studentom.

Rozwiązanie
folder <- "dane/cwiczenia/zepsute"

# 1. średnik i przecinek dziesiętny
p1 <- read.csv2(file.path(folder,
                          "01_ankieta_pl.csv"), 
                fileEncoding = "UTF-8")
str(p1)
## 'data.frame':    30 obs. of  6 variables:
##  $ id    : chr  "P01" "P02" "P03" "P04" ...
##  $ wiek  : int  19 24 31 33 30 22 34 23 30 20 ...
##  $ plec  : chr  "kobieta" "kobieta" "inna" "mężczyzna" ...
##  $ miasto: chr  "Łódź" "Białystok" "Wrocław" "Łódź" ...
##  $ stres : num  3.81 2.3 3.8 1.96 3.12 3.02 3.49 2.14 1.09 3.22 ...
##  $ rt_ms : int  656 595 680 593 554 630 535 773 604 662 ...
# 2. kodowanie
p2 <- read.csv(file.path(folder, "02_kodowanie.csv"), 
               fileEncoding = "windows-1250")
unique(p2$miasto)
## [1] "Łódź"      "Białystok" "Wrocław"   "Poznań"    "Kraków"
# 3. tabulator i braki danych
p3 <- read.delim(file.path(folder, "03_braki.tsv"), 
                 na.strings = c("-99", "brak"))
summary(p3[, c("wiek", "stres")])
##       wiek           stres      
##  Min.   :19.00   Min.   :1.090  
##  1st Qu.:21.75   1st Qu.:2.155  
##  Median :27.50   Median :3.100  
##  Mean   :26.71   Mean   :2.863  
##  3rd Qu.:31.00   3rd Qu.:3.485  
##  Max.   :35.00   Max.   :4.440  
##  NAs    :2       NAs    :3
# 4. trzy wiersze nagłówka: bierzemy nazwy z pierwszego, dane od czwartego
nazwy <- names(read.csv(file.path(folder,
                                  "04_qualtrics.csv"), 
                        nrows = 1))

p4 <- read.csv(file.path(folder, "04_qualtrics.csv"), 
               skip = 3, 
               header = FALSE, 
               col.names = nazwy)
str(p4)
## 'data.frame':    30 obs. of  6 variables:
##  $ id    : chr  "P01" "P02" "P03" "P04" ...
##  $ wiek  : int  19 24 31 33 30 22 34 23 30 20 ...
##  $ plec  : chr  "kobieta" "kobieta" "inna" "mężczyzna" ...
##  $ miasto: chr  "Łódź" "Białystok" "Wrocław" "Łódź" ...
##  $ stres : num  3.81 2.3 3.8 1.96 3.12 3.02 3.49 2.14 1.09 3.22 ...
##  $ rt_ms : int  656 595 680 593 554 630 535 773 604 662 ...
# 5. stała szerokość: 4 + 2 + 7 + 5 znaków
#    (uwaga: read.fwf liczy znaki - polskie litery w UTF-8 potrafią tu namieszać,
#     dlatego w tym pliku ich nie ma)
p5 <- read.fwf(file.path(folder, "05_stala_szerokosc.txt"),
               widths = c(4, 2, 7, 5),
               col.names = c("id", "wiek", "stres", "rt_ms"),
               strip.white = TRUE)
str(p5)
## 'data.frame':    30 obs. of  4 variables:
##  $ id   : chr  "P01" "P02" "P03" "P04" ...
##  $ wiek : int  19 24 31 33 30 22 34 23 30 20 ...
##  $ stres: num  3.81 2.3 3.8 1.96 3.12 3.02 3.49 2.14 1.09 3.22 ...
##  $ rt_ms: int  656 595 680 593 554 630 535 773 604 662 ...
# 6. Excel: drugi arkusz - po numerze albo (lepiej) po nazwie
if (requireNamespace("readxl", 
                     quietly = TRUE)) {
  print(readxl::excel_sheets(file.path(folder, 
                                       "06_excel_dwa_arkusze.xlsx")))
  p6 <- readxl::read_excel(file.path(folder,
                                     "06_excel_dwa_arkusze.xlsx"),
                           sheet = "dane")
  str(p6)
}
## [1] "opis" "dane"
## tibble [30 × 6] (S3: tbl_df/tbl/data.frame)
##  $ id    : chr [1:30] "P01" "P02" "P03" "P04" ...
##  $ wiek  : num [1:30] 19 24 31 33 30 22 34 23 30 20 ...
##  $ plec  : chr [1:30] "kobieta" "kobieta" "inna" "mężczyzna" ...
##  $ miasto: chr [1:30] "Łódź" "Białystok" "Wrocław" "Łódź" ...
##  $ stres : num [1:30] 3.81 2.3 3.8 1.96 3.12 3.02 3.49 2.14 1.09 3.22 ...
##  $ rt_ms : num [1:30] 656 595 680 593 554 630 535 773 604 662 ...





.RData

Ogromną zaletą formatu .RData jest to, że pozwala zapisać w jednym pliku wiele obiektów, także takich, które nie są ramkami danych. Pliki są kompresowane, więc zajmują mniej miejsca niż pliki tekstowe z tymi samymi danymi i mniej niż te same obiekty w pamięci RAM.

Możemy to sprawdzić, zapisując ten sam obiekt w kilku formatach (poznaliśmy już co najmniej dwa: .txt oraz .csv) – przykład w kodzie poniżej.

Do zapisu używamy funkcji save(), zaś do odczytu funkcji load()

save(mtcars, diamonds, iris, # dla przypomnienia - predefiniowane ramki
     file = 'dane/przyklad.RData')


# Ile miejsca zajmuje ten sam zbiór w różnych postaciach?
write.csv(diamonds, "dane/diamonds_test.csv", row.names = FALSE)
saveRDS(diamonds, "dane/diamonds_test.rds")

rozmiary_MB <- c(
  RAM = as.numeric(object.size(diamonds)),
  csv = file.size("dane/diamonds_test.csv"),
  rds = file.size("dane/diamonds_test.rds")
) / 1024^2
round(rozmiary_MB, 2)
##  RAM  csv  rds 
## 3.30 2.70 0.49
file.remove("dane/diamonds_test.csv", "dane/diamonds_test.rds") # sprzątamy po sobie
## [1] TRUE TRUE


Usuńmy trzy obiekty (mtcars, diamonds, iris), które potencjalnie mamy w Global Environment, żeby upewnić się co do prawidłowego działania funkcji load()

rm(mtcars, diamonds, iris)


I wczytajmy ponownie.

load('dane/przyklad.RData')


Są w środowisku? Spójrzmy w RStudio lub sprawdźmy kodem.

c("mtcars","iris") %in% ls()
## [1] TRUE TRUE



“Okienko po kliknięciu na dane w oknie eksploratora Windows”
“Okienko po kliknięciu na dane w oknie eksploratora Windows”



Sporą wadą plików .RData i poleceń save() i load() jest to, że przy wczytywaniu po cichu nadpisują obiekty o takiej samej nazwie, które są już w środowisku. Przykładowo: jeśli mamy już zbiór o nazwie diamonds (np. po wielu godzinach czyszczenia), jego miejsce zajmie obiekt o tej samej nazwie z pliku .RData – bez żadnego ostrzeżenia.

Drugą wadą jest to, że format jest czytelny tylko dla R – współpracownik używający Pythona, SPSS-a nie otworzy takiego pliku.

Bezpieczniejszy sposób wczytania: load() do osobnego środowiska.

schowek <- new.env()
load("dane/przyklad.RData", envir = schowek)
ls(schowek)            # co było w pliku?
## [1] "diamonds" "iris"     "mtcars"
head(schowek$mtcars, 2) # sięgamy po obiekt bez ryzyka nadpisania
##               mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4      21   6  160 110  3.9 2.620 16.46  0  1    4    4
## Mazda RX4 Wag  21   6  160 110  3.9 2.875 17.02  0  1    4    4





.rda

.rda to dokładnie ten sam format co .RData – tylko inna konwencja nazewnictwa.

Niektórzy preferują .rda dla pojedynczych obiektów, a .RData dla zrzutów całej sesji, ale technicznie to identyczne pliki.

Właściwości techniczne

  • Format binarny (nieczytelny dla człowieka).

  • Kompresowany (domyślnie gzip; można zmienić np. na xz dla lepszej kompresji: save(..., compress = "xz")).

  • Można go przenosić między systemami (Windows, macOS, Linux).

  • Obsługuje dowolne typy obiektów R – nie tylko dane tabelaryczne.

  • Nie przechowuje środowiska globalnego jako całości (chyba że użyjesz save.image()).

save.image(file = "session.RData")





.Rds

To alternatywny dla .RData format plików. Również dobrze skompresowany, ale przy wczytywaniu sami nadajemy nazwę obiektowi – nic nie zostanie po cichu nadpisane. W tym formacie możemy jednak zapisać tylko jeden obiekt (choć może to być np. lista wielu ramek).


Zapis

# zapis
saveRDS(diamonds, 
        file = 'dane/diamonds.rds')


Wczytanie

# wczytanie
diamonds <- readRDS('dane/diamonds.rds')


Podgląd

head(diamonds,3)
## # A tibble: 3 × 10
##   carat cut     color clarity depth table price     x     y     z
##   <dbl> <ord>   <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1  0.23 Ideal   E     SI2      61.5    55   326  3.95  3.98  2.43
## 2  0.21 Premium E     SI1      59.8    61   326  3.89  3.84  2.31
## 3  0.23 Good    E     VS1      56.9    65   327  4.05  4.07  2.31





Excel .xlsx

Pakietów do wczytywania Excel’owych plików jest wiele. Nauczmy się robić to używając np. pakietu readxl.

Instalacja i uruchomienie pakietów.

# install.packages("readxl")
# install.packages("writexl")
library(readxl)
library(writexl)

Obejrzyjmy zbiór poza RStudio.

A następnie wczytajmy dane.

Najpierw sprawdźmy, jakie arkusze są w pliku:

excel_sheets("dane/Titanic2.xlsx")
## [1] "Sheet3"        "titanic"       "titanic_pivot"
# read_excel(path, sheet = NULL)
titanic1 <- read_excel(
  path = "dane/Titanic2.xlsx"
  ) # bez podania dodatkowych argumentów
head(titanic1, 10)
## # A tibble: 10 × 4
##    `Sum of passenger_id` `Column Labels`   ...3 ...4       
##    <chr>                           <dbl>  <dbl> <chr>      
##  1 Row Labels                          0      1 Grand Total
##  2 0                              490200  91475 581675     
##  3 1                               20437   9703 30140      
##  4 2                               66779  11766 78545      
##  5 3                              402984  70006 472990     
##  6 1                              110284 165436 275720     
##  7 1                                 571  21615 22186      
##  8 2                                5428  44001 49429      
##  9 3                              104285  99820 204105     
## 10 Grand Total                    600484 256911 857395


Chcieliśmy inny sheet (tab) i raw data, zatem…

titanic2 <- read_excel(
  path = "dane/Titanic2.xlsx",
  sheet = 2 
  )
head(titanic2, 5)
## # A tibble: 5 × 15
##   passenger_id pclass survived name    sex    age sibsp parch ticket  fare cabin
##          <dbl>  <dbl>    <dbl> <chr> <dbl>  <dbl> <dbl> <dbl> <chr>  <dbl> <chr>
## 1            1      1        1 Alle…     1 29         0     0 24160   211. B5   
## 2            2      1        1 Alli…     0  0.917     1     2 113781  152. C22 …
## 3            3      1        0 Alli…     1  2         1     2 113781  152. C22 …
## 4            4      1        0 Alli…     0 30         1     2 113781  152. C22 …
## 5            5      1        0 Alli…     1 25         1     2 113781  152. C22 …
## # ℹ 4 more variables: embarked <chr>, boat <chr>, body <dbl>, home.dest <chr>


A co jeśli mamy taki plik? Obejrzyjmy go w Excelu.

Wówczas

titanic3 <- read_excel(
  path = "dane/Titanic3.xlsx",
  sheet = 2, 
  skip = 3, 
  col_names = F
  )
head(titanic3, 2)
## # A tibble: 2 × 15
##    ...1  ...2  ...3 ...4   ...5   ...6  ...7  ...8 ...9  ...10 ...11 ...12 ...13
##   <dbl> <dbl> <dbl> <chr> <dbl>  <dbl> <dbl> <dbl> <chr> <dbl> <chr> <chr> <chr>
## 1     1     1     1 Alle…     1 29         0     0 24160  211. B5    S     2    
## 2     2     1     1 Alli…     0  0.917     1     2 1137…  152. C22 … S     11   
## # ℹ 2 more variables: ...14 <dbl>, ...15 <chr>


Niestety bez nagłówków kolumn. Czasem, gdy cyklicznie otrzymujemy od kogoś zrzuty w Excel’u, teoretycznie o tej samej strukturze, ale przez przypadek przycięte przez użytkownika, możemy nagłówki zaczerpnąć z poprzedniej wersji zbioru.

names(titanic3) <- names(titanic2) # ale tu mieliśmy szczęśliwie skąd pożyczyć

head(titanic3, 3)
## # A tibble: 3 × 15
##   passenger_id pclass survived name    sex    age sibsp parch ticket  fare cabin
##          <dbl>  <dbl>    <dbl> <chr> <dbl>  <dbl> <dbl> <dbl> <chr>  <dbl> <chr>
## 1            1      1        1 Alle…     1 29         0     0 24160   211. B5   
## 2            2      1        1 Alli…     0  0.917     1     2 113781  152. C22 …
## 3            3      1        0 Alli…     1  2         1     2 113781  152. C22 …
## # ℹ 4 more variables: embarked <chr>, boat <chr>, body <dbl>, home.dest <chr>


Precyzyjne wskazanie zakresu i typów kolumn

Zamiast skip możemy wskazać dokładny zakres komórek – tak, jak zaznaczylibyśmy go myszką w Excelu (range = "B4:F20") albo tylko zakres wierszy:

titanic3b <- read_excel(
  path = "dane/Titanic3.xlsx",
  sheet = 2,
  range = cell_rows(4:13),  # tylko wiersze 4-13
  col_names = FALSE
)
dim(titanic3b)
## [1] 10 15

Argument col_types pozwala wymusić typy kolumn ("text", "numeric", "date", "logical", "skip", "guess"). Przydaje się szczególnie wtedy, gdy Excel „pomógł” i zamienił coś na datę.


Zapis do Excela

Pakiet writexl wczytaliśmy, więc go użyjmy. Nazwana lista ramek daje plik z wieloma arkuszami

write_xlsx(
  list(dane = titanic2,
       info = data.frame(zrodlo = "Titanic2.xlsx, arkusz 2",
                         liczba_wierszy = nrow(titanic2),
                         data_eksportu  = as.character(Sys.Date()))),
  path = "dane/titanic_eksport.xlsx"
)
excel_sheets("dane/titanic_eksport.xlsx")
## [1] "dane" "info"


Excel – kilka historii ku przestrodze

Co wydarzyło się później?

  • 2013 r.: doktorant ekonomii Thomas Herndon z University of Massachusetts Amherst próbował odtworzyć wyniki pracy w ramach zadania na zajęcia.

  • Herndon wraz z ekonomistami Michaelem Ashem i Robertem Pollinem przeanalizował udostępnione dane oraz arkusz kalkulacyjny Reinhart i Rogoffa.

  • Badacze zidentyfikowali błąd w formule Excela, a także inne problemy: pominięte dane i kontrowersyjny sposób ważenia obserwacji.

  • Ich analiza wykazała, że dla krajów z długiem przekraczającym 90% PKB średnie tempo wzrostu w latach 1946–2009 wynosiło 2,2%, a nie −0,1%, jak wynikało z pierwotnej analizy.





SPSS (.sav)


Należy pamiętać, jak wygląda praca w programie SPSS. Mamy tam dwa widoki: pierwszy umożliwia podejrzenie danych, drugi pozwala przyjrzeć się zmiennym i ich kategoriom, jeśli zmienna ma taki charakter (factor w R)

“Widok SPSS’a”

Instalacja i uruchomienie pakietów.

# install.packages("haven")
library(haven)


I wczytanie danych.

spss_df <- read_sav(file = "dane/IBM SPSS Sample Files/accidents.sav")


Jak podejrzeć nazwy kategorii?

# attr(spss_df$gender, "label")
attr(spss_df$gender, "labels")
##   Male Female 
##      0      1
class(spss_df)
## [1] "tbl_df"     "tbl"        "data.frame"
typeof(spss_df)
## [1] "list"
# so...
is.data.frame(spss_df) # how?
## [1] TRUE
# [NOWE] Odpowiedź: class() zwraca kilka klas naraz - "tbl_df", "tbl", "data.frame".
# tibble JEST ramką danych (dziedziczy po data.frame), tylko z dodatkami.
# A typeof() pokazuje, z czego ramka jest zbudowana "pod spodem": to lista kolumn (wektorów).

summary(spss_df)
##      agecat         gender        accid            pop        
##  Min.   :1.00   Min.   :0.0   Min.   :54123   Min.   :187791  
##  1st Qu.:1.25   1st Qu.:0.0   1st Qu.:57334   1st Qu.:196416  
##  Median :2.00   Median :0.5   Median :60967   Median :199633  
##  Mean   :2.00   Mean   :0.5   Mean   :60801   Mean   :199035  
##  3rd Qu.:2.75   3rd Qu.:1.0   3rd Qu.:64610   3rd Qu.:202586  
##  Max.   :3.00   Max.   :1.0   Max.   :66804   Max.   :208239


Etykiety SPSS w R: klasa haven_labelled

read_sav() nie zamienia kategorii od razu na czynniki – zostawia liczby z dołączonymi etykietami (klasa haven_labelled). Dzięki temu nie tracimy żadnej informacji, ale do analiz zwykle trzeba zdecydować, co z etykietami zrobić:

class(spss_df$gender)
## [1] "haven_labelled" "vctrs_vctr"     "double"
print_labels(spss_df$gender)       # czytelny podgląd: kod = etykieta
## 
## Labels:
##  value  label
##      0   Male
##      1 Female
spss_f <- as_factor(spss_df)       # wszystkie zmienne z etykietami -> czynniki
str(spss_f)
## tibble [6 × 4] (S3: tbl_df/tbl/data.frame)
##  $ agecat: Factor w/ 3 levels "Under 21","21-25",..: 1 2 3 1 2 3
##   ..- attr(*, "label")= chr "Age category"
##  $ gender: Factor w/ 2 levels "Male","Female": 2 2 2 1 1 1
##   ..- attr(*, "label")= chr "Gender"
##  $ accid : num [1:6] 57997 57113 54123 63936 64835 ...
##   ..- attr(*, "label")= chr "Accidents"
##   ..- attr(*, "format.spss")= chr "F8.0"
##  $ pop   : num [1:6] 198522 203200 200744 187791 195714 ...
##   ..- attr(*, "label")= chr "Population at risk"
##   ..- attr(*, "format.spss")= chr "F8.0"
spss_num <- zap_labels(spss_df)    # odwrotnie: usuwamy etykiety, zostają same liczby
str(spss_num)
## tibble [6 × 4] (S3: tbl_df/tbl/data.frame)
##  $ agecat: num [1:6] 1 2 3 1 2 3
##   ..- attr(*, "label")= chr "Age category"
##   ..- attr(*, "format.spss")= chr "F4.0"
##  $ gender: num [1:6] 1 1 1 0 0 0
##   ..- attr(*, "label")= chr "Gender"
##   ..- attr(*, "format.spss")= chr "F4.0"
##  $ accid : num [1:6] 57997 57113 54123 63936 64835 ...
##   ..- attr(*, "label")= chr "Accidents"
##   ..- attr(*, "format.spss")= chr "F8.0"
##  $ pop   : num [1:6] 198522 203200 200744 187791 195714 ...
##   ..- attr(*, "label")= chr "Population at risk"
##   ..- attr(*, "format.spss")= chr "F8.0"
# i zapis z powrotem do SPSS (np. dla promotora):
write_sav(spss_df, "dane/accidents_kopia.sav")

::::


Inny sposób (z użyciem innego, starszego pakietu foreign). Uwaga: domyślnie read.spss() zwraca listę, a nie ramkę danych – trzeba dodać argument to.data.frame = TRUE.

# install.packages("foreign")
library(foreign)
spss_df2 <- read.spss(file = "dane/IBM SPSS Sample Files/accidents.sav",
                      use.value.labels = TRUE, # ten argument zamienia kody na etykiety (czynniki)
                      to.data.frame = TRUE     # [POPRAWKA] bez tego dostajemy listę
                      )

class(spss_df2)
## [1] "data.frame"
typeof(spss_df2)
## [1] "list"
is.data.frame(spss_df2)
## [1] TRUE
summary(spss_df2)
##       agecat     gender      accid            pop        
##  Under 21:2   Male  :3   Min.   :54123   Min.   :187791  
##  21-25   :2   Female:3   1st Qu.:57334   1st Qu.:196416  
##  26-30   :2              Median :60967   Median :199633  
##                          Mean   :60801   Mean   :199035  
##                          3rd Qu.:64610   3rd Qu.:202586  
##                          Max.   :66804   Max.   :208239

Dziś zalecaną drogą jest haven (aktywnie rozwijany, obsługuje nowsze wersje plików SPSS, Staty i SAS-a). foreign warto znać, bo pojawia się w starszym kodzie.




Porządkowanie nazw kolumn

Pliki z Excela i ankiet internetowych mają często nazwy kolumn typu "Wiek (lata) ", "Płeć" czy "Wynik %". W R trzeba je potem otaczać grawisami: dane$`Wiek (lata) `.


Funkcja clean_names() z pakietu {janitor} zamienia je na bezpieczny format (małe litery, podkreślenia, bez polskich znaków).

# install.packages("janitor")
brzydkie <- data.frame(`Wiek (lata) ` = c(21, 24),
                       `Płeć` = c("K", "M"),
                       `Wynik %` = c(80, 75),
                       check.names = FALSE)
names(brzydkie)
## [1] "Wiek (lata) " "Płeć"         "Wynik %"
names(janitor::clean_names(brzydkie))
## [1] "wiek_lata"     "plec"          "wynik_percent"
# wersja z bazowego R - mniej elegancka, ale bez dodatkowego pakietu:
make.names(names(brzydkie))
## [1] "Wiek..lata.." "Płeć"         "Wynik.."




Internet

Znając link do strony, na której ktoś udostępnia interesujący nas zbiór danych, możemy posłużyć się funkcjami dostępnymi w R, żeby taką ramkę ściągnąć i zacząć „obrabiać” bezpośrednio w środowisku R.

(investment_annual_summary <- readr::read_csv("https://assets.datacamp.com/production/repositories/5756/datasets/d0251f26117bbcf0ea96ac276555b9003f4f7372/investment_annual_summary.csv", show_col_types = FALSE))
## # A tibble: 42 × 3
##    fiscal_year region                          dollars_in_millions
##          <dbl> <chr>                                         <dbl>
##  1        2012 East Asia and the Pacific                      2548
##  2        2012 Europe and Central Asia                        2915
##  3        2012 Latin America and the Caribbean                3680
##  4        2012 Middle East and North Africa                   2210
##  5        2012 South Asia                                     1312
##  6        2012 Sub-Saharan Africa                             2733
##  7        2013 East Asia and the Pacific                      2873
##  8        2013 Europe and Central Asia                        3261
##  9        2013 Latin America and the Caribbean                4822
## 10        2013 Middle East and North Africa                   2038
## # ℹ 32 more rows


# przykład z internetowego tutorial'u Dr Eugene O'Loughlin'a
url = "https://raw.githubusercontent.com/eoloughlin/How-To-R/master/24_Data_File.csv"
priceData <- read.csv(url)

head(priceData, 6) # Zobacz sześć pierwszych wierszy
##   Oil Gold
## 1  50 1267
## 2  46 1238
## 3  54 1157
## 4  55 1192
## 5  55 1234
## 6  52 1231
tail(priceData, 6) # Zobacz sześć ostatnich wierszy
##    Oil Gold
## 7   53 1267
## 8   51 1246
## 9   47 1260
## 10  49 1237
## 11  51 1283
## 12  55 1314
names(priceData) # sprawdź nazwy kolumn
## [1] "Oil"  "Gold"

Linki w internecie żyją krótko, a dane pod tym samym adresem mogą się zmienić. Dlatego w projekcie badawczym dobrze jest raz pobrać plik na dysk i dalej pracować na lokalnej kopii (zapisując w skrypcie, skąd i kiedy pochodzi):

# pobrano: 2025-10-27, źródło: tutorial E. O'Loughlina
download.file(url,
              destfile = "dane/priceData_kopia.csv", 
              mode = "wb")


Inne przydatne źródła „z internetu”:

  • googlesheets4::read_sheet() – arkusze Google (np. odpowiedzi z_ Google Forms),

  • osfr – pobieranie plików z projektów na OSF (Open Science Framework), gdzie psychologowie udostępniają dane i materiały do badań,

  • eurostat,

  • bdl (Bank Danych Lokalnych GUS),

  • WDI (Bank Światowy) – pakiety do oficjalnych statystyk.




Zewnętrzne bazy danych

Dane w firmach i dużych projektach często żyją w bazach danych, a SQL to kolejny język, który się przyda.

Przykład połączenia z bazą AACT, czyli kopią danych z portalu https://clinicaltrials.gov/ – publicznego rejestru badań klinicznych. Dostęp do bazy jest bezpłatny po założeniu konta. Korzystając z pakietu do obsługi PostgreSQL, możemy wysyłać do bazy zapytania w języku SQL.


###### Packages ###### 

# install.packages("DBI")
# install.packages("RPostgres")   # nowszy następca RPostgreSQL
library(DBI)
library(RPostgres)

###### Credentials ###### 
# https://aact.ctti-clinicaltrials.org/
Hostname        = "aact-db.ctti-clinicaltrials.org"
Port            =  5432
Database_name   = "aact"

# NIGDY nie wpisujemy loginu i hasła wprost do skryptu (skrypty trafiają na GitHuba,# do maili, do prezentacji...).

###### Connection ###### 
con <- dbConnect(RPostgres::Postgres(),
                 dbname   = Database_name,
                 host     = Hostname,
                 port     = Port,
                 user     = "MichalKichal",
                 password = "haslo"))
class(con)

# Wersja ze starszym pakietem RPostgreSQL (wciąż działa):
# library(RPostgreSQL)
# drv <- dbDriver("PostgreSQL")
# con <- dbConnect(drv, 
#                  dbname = Database_name, 
#                  host = Hostname, 
#                  port = Port,
#                  user = Sys.getenv("AACT_USER"), 
#                  password = Sys.getenv("AACT_PASS"))

###### Info ###### 
# https://aact.ctti-clinicaltrials.org/schema
# https://aact.ctti-clinicaltrials.org/data_dictionary

###### Query ###### 
studyTypes <- dbGetQuery(con,
                         "SELECT DISTINCT study_type FROM studies")

studies <- dbGetQuery(con,                      
                      "SELECT * FROM studies LIMIT 20")

dbDisconnect(con)                               # zamykamy połączenie po pracy




Przykład z pracy w firmie: serwer Microsoft SQL Server, łączenie przez sterownik ODBC.

############### libraries ###############
library(DBI)
library(odbc)
library(data.table)

############### połączenie (serwer firmowy) ###############
con <- dbConnect(odbc(), 
                 Driver = "SQL Server",
                 Server = "nazwa_serwera", 
                 UID    = Sys.getenv("DB_USER"),
                 PWD    = rstudioapi::askForPassword("Podaj hasło do bazy:"))
# askForPassword() przyjmuje TEKST ZACHĘTY wyświetlany w okienku, a nie hasło.
# Okienko pojawi się w RStudio, hasło wpisujemy ręcznie i nie zostaje ono w skrypcie.

############### queries ###############

query <- c("SELECT TOP 10 * FROM CT.dbo.facilities f")

studies <- data.table(
  dbGetQuery(con, query)
  )

###
query <- c(
"SELECT f.nct_id AS project_id, f.country, COUNT(f.id) AS sites_count
FROM CT.dbo.facilities f
    JOIN (SELECT DISTINCT st.nct_id 
            FROM CT.dbo.studies st
            LEFT JOIN CT.dbo.sponsors sp ON sp.nct_id = st.nct_id
            LEFT JOIN CT.dbo.calculated_values cv ON st.nct_id = cv.nct_id
            LEFT JOIN CT.dbo.conditions cn ON st.nct_id = cn.nct_id
            LEFT JOIN CT.dbo.eligibilities e ON st.nct_id = e.nct_id
            WHERE
                st.start_date > '2010-01-01' AND st.study_type = 'Interventional'
                AND sp.agency_class = 'INDUSTRY' AND sp.lead_or_collaborator = 'lead'
            ) s ON f.nct_id = s.nct_id
WHERE 
    f.country IS NOT NULL 
  AND f.country IN ('Poland', 'Austria','Belgium','Bosnia','Denmark','Estonia','Finland','Latvia',
                              'Lithuania','Macedonia','Netherlands','Norway')
GROUP BY 
    f.nct_id, f.country;"
)

SiteCount <- data.table(
  dbGetQuery(con, 
             query)
  )

dbDisconnect(con)




Wczytanie i zapis wielu plików

Wyobraźmy sobie sytuację, że nie mamy licencji na SPSS-a, a znalezione pliki są zapisane w formacie .sav. Jeśli chcemy mimo wszystko skorzystać z tych danych, możemy je w R przekonwertować do dowolnego innego formatu. Poniżej zajawka, jak to zrobić, stojąc przed koniecznością konwersji wielu plików.

library(haven)

# tylko pliki .sav od razu z pełną ścieżką - nie trzeba potem sklejać paste0()
sciezki <- list.files("dane/IBM SPSS Sample Files",
                      pattern = "\\.sav$",
                      full.names = TRUE)
sciezki

# wczytywanie wielu plików na raz - wersja z pętlą for
dane_spss_mult <- list()
for (i in seq_along(sciezki)) { # == 1:length(sciezki), ale bezpieczne dla pustego wektora
  dane_spss_mult[[i]] <- read_sav(sciezki[i])
}

# lapply() stosuje funkcję do każdego elementu
dane_spss_mult <- lapply(sciezki, read_sav)

#  nazwy elementów listy bez ścieżki i bez rozszerzenia: "bankloan" zamiast "bankloan.sav"
names(dane_spss_mult) <- tools::file_path_sans_ext(basename(sciezki))
names(dane_spss_mult)

# sprawdzenie losowego zbioru
head(dane_spss_mult$bankloan, 4)

# Pojedyncza ramka
accidents <- dane_spss_mult$accidents   # pojedynczy zbiór - jak najbardziej
class(accidents)

getwd() # sprawdzenie ścieżki

# zapisywanie wielu plików na raz (np. do .csv) - osobny folder na wyniki
dir.create("dane/spss_csv", showWarnings = FALSE)

for (nazwa in names(dane_spss_mult)) {
  write.csv2(x = dane_spss_mult[[nazwa]],               #  
             file = file.path("dane/spss_csv",          # 
                              paste0(nazwa, ".csv")),   # 
             row.names = FALSE)
}
list.files("dane/spss_csv")




Inne formaty i narzędzia warte poznania

  • rio::import() / rio::export() – jedna funkcja do prawie wszystkich formatów; format rozpoznawany jest po rozszerzeniu pliku (import("dane.sav"), import("dane.xlsx"), export(dane, "dane.csv")).

  • Parquet (arrow) – nowoczesny, kolumnowy format binarny: mały, bardzo szybki i – w przeciwieństwie do .rds – czytelny także w Pythonie, Julii czy bazach danych. Coraz częściej spotykany przy dużych zbiorach.

  • duckdb – „SQL bez serwera”: pozwala pisać zapytania SQL bezpośrednio do plików CSV i Parquet, nawet większych niż pamięć RAM.

  • Stata i SAS – haven::read_dta(), haven::read_sas().

  • datapasta – dodatek do RStudio, który wkleja tabelkę skopiowaną ze strony WWW lub Excela jako gotowy kod R.

# install.packages(c("rio", "arrow"))
rio::export(diamonds, "dane/diamonds_rio.csv")
head(rio::import("dane/diamonds_rio.csv"), 
     2)
##   carat     cut color clarity depth table price    x    y    z
## 1  0.23   Ideal     E     SI2  61.5    55   326 3.95 3.98 2.43
## 2  0.21 Premium     E     SI1  59.8    61   326 3.89 3.84 2.31
arrow::write_parquet(diamonds, 
                     "dane/diamonds.parquet")
file.size("dane/diamonds.parquet") / 1024^2      # rozmiar w MB - porównaj z csv i rds
## [1] 0.4917231
head(arrow::read_parquet("dane/diamonds.parquet"), 2)
## # A tibble: 2 × 10
##   carat cut     color clarity depth table price     x     y     z
##   <dbl> <ord>   <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
## 1  0.23 Ideal   E     SI2      61.5    55   326  3.95  3.98  2.43
## 2  0.21 Premium E     SI1      59.8    61   326  3.89  3.84  2.31





Zarządzanie plikami i folderami z poziomu R

dir()         # pliki i foldery w katalogu roboczym

list.files()  # to samo co dir()

list.files("dane", pattern = "\\.csv$")  # tylko pliki .csv z folderu dane

list.dirs("dane")   # lista katalogów (rekurencyjnie, z podkatalogami)

dir.create("dane/Dane2", showWarnings = FALSE)  # stworzenie folderu [ZMIANA] ścieżka względna

dir.exists("dane")  # sprawdzenie czy folder istnieje

file.create("dane/script.R")     # stworzenie pliku

file.exists("dane/script.R")     # sprawdzanie czy plik istnieje

file.size("dane/script.R")       # wielkość pliku (w bajtach)

file.info("dane/script.R")       # info o pliku (rozmiar, daty, uprawnienia)

file.copy(from = "dane/script.R", 
          to = "dane/Dane2")     # skopiowanie pliku do innego folderu

file.rename("dane/script.R", "dane/script2.R") # zmiana nazwy pliku

file.edit("dane/script2.R")      # edycja pliku (otwiera edytor - tylko w pracy interaktywnej)

file.remove("dane/script2.R", "dane/Dane2/script.R") # skasowanie plików

unlink("dane/Dane2", recursive = TRUE)                # skasowanie folderu z zawartością - OSTROŻNIE!





Przy zarządzaniu plikami warto jeszcze wiedzieć, że pliki można programowo rozpakowywać poleceniem unzip() i pakować poleceniem zip():

unzip(zipfile = "dane/ibm_spss_sample_files_0.zip",
      exdir   = "dane/Dane2")

# utils::zip() na Windowsie wymaga zewnętrznego programu (np. z Rtools),
# dlatego wygodniej użyć pakietu {zip}:
# install.packages("zip")
zip::zip(zipfile = "dane/wyniki.zip", files = c("dane/cities.txt", "dane/daneusa.csv"))





Czas to pieniądz


Porównanie szybkości funkcji do wczytywania plików z danymi

# install.packages("microbenchmark")
library(microbenchmark)

compare <- microbenchmark(read.csv2("dane/diamonds2.csv"), 
                          readr::read_csv("dane/diamonds2.csv"),
                          data.table::fread("dane/diamonds2.csv"),
                          readRDS("dane/diamonds2.rds"),
                          load("dane/diamonds2.rda"),
                          times = 10)

compare
## Unit: milliseconds
##                                     expr      min       lq      mean    median
##          read.csv2("dane/diamonds2.csv") 123.7589 136.5258 144.04275 145.84875
##    readr::read_csv("dane/diamonds2.csv") 339.7290 351.2071 356.14491 354.81310
##  data.table::fread("dane/diamonds2.csv")  15.5175  16.3487  18.45216  16.66390
##            readRDS("dane/diamonds2.rds")   9.8995  10.2747  13.35767  10.70500
##               load("dane/diamonds2.rda")   9.4837  10.3942  16.06245  11.48005
##        uq      max neval
##  147.8109 168.8962    10
##  358.9118 377.4548    10
##   17.8100  33.2377    10
##   11.5433  36.6326    10
##   12.4638  36.9333    10
boxplot(compare, unit = "ms", log = TRUE, las = 2, ylab = "czas [ms], skala log")

# library(knitr)
# kable(compare)


# ciekawostka
df <- data.frame(col1 = seq(1,10000, by = 1), 
                 col2 = rep(x = 1:1000, 10), 
                 col3 = rnorm(10000))

m <- as.matrix(df)

microbenchmark(df[,1], m[,1], times = 100)
## Unit: microseconds
##     expr  min   lq   mean median    uq   max neval
##  df[, 1]  2.9  3.4  6.908   3.80  4.45 120.0   100
##   m[, 1] 16.4 29.7 46.954  50.25 51.95 148.8   100
microbenchmark(df[1,], m[1,], times = 100)
## Unit: nanoseconds
##     expr   min    lq  mean median    uq    max neval
##  df[1, ] 15100 15900 20230  16350 20000 162200   100
##   m[1, ]   200   300   635    400   600  15700   100

Skąd takie wyniki? Ramka danych to lista kolumn – każda kolumna jest osobnym wektorem. Pobranie kolumny to tylko „wskazanie palcem” jednego z nich, więc jest szybkie. Pobranie wiersza wymaga zajrzenia do każdej kolumny osobno i złożenia z tych kawałków nowej, jednowierszowej ramki – a to kosztuje.

Macierz to natomiast jeden długi wektor z informacją o wymiarach, zapisany kolumna po kolumnie. Wyciągnięcie wiersza to po prostu pobranie co n-tego elementu – bez budowania nowej ramki, dlatego jest wielokrotnie szybsze. Wniosek praktyczny: jeśli w pętli wielokrotnie sięgamy po wiersze danych liczbowych, warto rozważyć macierz.





Profilowanie

# install.packages("profvis")
library(profvis)

profvis({

  #create dataset
data <- data.frame(y=c(6, 7, 7, 9, 12, 13, 13, 15, 16, 19, 22, 23, 23, 25, 26),
                   x=c(1, 2, 2, 3, 4, 4, 5, 6, 6, 8, 9, 9, 11, 12, 12))

# Tworzy ramkę danych data z dwiema kolumnami:
# y (wartości zmiennej zależnej)
# x (wartości zmiennej niezależnej)
model <- lm(y~x, data=data)
summary(model)
  

library(ggplot2)

wykres <- ggplot(data,aes(x, y)) +
  geom_point() + # Wykres  punktowy geom_point() 
  geom_smooth(method='lm') # wraz z dopasowaną linią regresji 
print(wykres) # [POPRAWKA] bez print() wykres nie jest rysowany wewnątrz profvis(),
              # więc czas rysowania nie trafiał do profilu

}) # koniec Profilowania

Cały blok kodu jest profilowany za pomocą funkcji profvis(). Profilowanie pozwala analizować, ile czasu zajmują poszczególne etapy wykonywania kodu (np. tworzenie danych, dopasowanie modelu, generowanie wykresu).

Innymi słowy, kod tworzy prosty model regresji liniowej, wizualizuje go za pomocą wykresu punktowego z linią regresji, a funkcja profvis() z pakietu {profvis} analizuje jego wydajność.



Zasoby komputera

Sprawdzanie zasobów własnej maszyny w konsoli R

# install.packages("benchmarkme")
library(benchmarkme)
get_cpu()
## $vendor_id
## [1] NA
## 
## $model_name
## [1] NA
## 
## $no_of_cores
## [1] 14


library(parallel)
detectCores() # == get_cpu()$no_of_cores
## [1] 14





Podsumowanie

Ściągawka: wczytywanie i zapis

Format Wczytanie Zapis Pakiet
tekst z separatorem read.table(), read.csv(), read.csv2(), read.delim() write.table(), write.csv(), write.csv2() utils (bazowy)
tekst z separatorem read_csv(), read_csv2(), read_tsv(), read_delim() write_csv(), write_csv2() readr
tekst z separatorem fread() fwrite() data.table
stała szerokość read.fwf(), read_fwf() – utils, readr
.RData / .rda load() save(), save.image() bazowy
.rds readRDS() saveRDS() bazowy
Excel read_excel(), excel_sheets() write_xlsx() readxl, writexl
SPSS / Stata / SAS read_sav(), read_dta(), read_sas() write_sav(), write_dta() haven
Parquet read_parquet() write_parquet() arrow
„wszystko” import() export() rio


Najważniejsze argumenty do zapamiętania:

  • header/col_names,

  • sep/delim,

  • dec,

  • na.strings/na,

  • skip,

  • fileEncoding/locale(encoding = ...),

  • sheet, range,

  • col_types.


Lista kontrolna po wczytaniu:

  • str() (typy kolumn),

  • summary() (dziwne wartości: -99, 999),

  • head()/tail() (czy nie ma śmieci na końcu pliku),

  • dim() (czy liczba wierszy się zgadza),

  • unique() na zmiennych tekstowych (polskie znaki, literówki w kategoriach).





W jakim formacie oddać dane, żeby dało się je otworzyć za 20 lat?

Wyobraźcie sobie, że kończycie badanie do pracy magisterskiej i chcecie umieścić dane w publicznym repozytorium, zgodnie z zasadami FAIR (Findable, Accessible, Interoperable, Reusable).

Do wyboru: .csv, .xlsx, .sav, .rds, .rda.

  • Który format otworzy ktoś bez R i bez płatnych programów?

  • Gdzie zapisać informacje, których CSV nie przechowuje (etykiety odpowiedzi, kolejność poziomów, treść pytań)? Podpowiedź: książka kodów (codebook) albo plik README.

  • Czy w ogóle wszystkie dane mogą trafić do repozytorium (RODO, anonimizacja)?


Najczęstsza odpowiedź ekspertów: CSV w UTF-8 + książka kodów + skrypt R, który odtwarza wszystkie analizy.





Źródła i inspiracje





techniczne

pomarancz

szary

zielony

czerwony

niebieski