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"
Ś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("...").
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"
Alternatywny sposób ustawiania working directory poprzez wyklikanie prawego dolnego okna ->
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/
Ś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.
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()
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()
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ę?
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.
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).
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().
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.
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.
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.
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.
readrJest 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>
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.tableTak 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.
„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.
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 ...
.RDataOgromną 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
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")
.RdsTo 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
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ę.
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
Geny, które stały się datami. Excel automatycznie zamieniał nazwy genów takich jak SEPT2 czy MARCH1 na daty (2 września, 1 marca). Ziemann i współpracownicy (2016, Genome Biology) znaleźli takie błędy w mniej więcej co piątym artykule genetycznym z dołączonymi arkuszami. W 2020 roku komitet nazewnictwa genów (HGNC) zmienił nazwy genów (np. SEPT1 → SEPTIN1) – łatwiej było zmienić biologię niż Excela. https://pmc.ncbi.nlm.nih.gov/articles/PMC4994289/?utm_source=chatgpt.com
Zgubione wyniki COVID-19. W październiku 2020
roku angielska agencja Public Health England nie przekazała dalej blisko
16 tysięcy pozytywnych wyników testów. Dane przechodziły przez stary
format .xls, który mieści maksymalnie 65 536 wierszy –
reszta po prostu znikała. https://www.theguardian.com/politics/2020/oct/05/how-excel-may-have-caused-loss-of-16000-covid-tests-in-england?utm_source=chatgpt.com
Austerity z arkusza. W 2010 roku ekonomiści Carmen Reinhart i Kenneth Rogoff opublikowali pracę Growth in a Time of Debt. Analizowała ona związek między poziomem długu publicznego a wzrostem gospodarczym. Wyniki sugerowały szczególnie słaby wzrost w krajach, w których dług publiczny przekraczał 90% PKB. Praca zyskała duży wpływ na debatę o polityce oszczędnościowej, czyli austerity. https://academic.oup.com/cje/article-abstract/38/2/257/1714018?redirectedFrom=fulltext
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.
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)
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.
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.."
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.
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)
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")
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
Rdir() # 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"))
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.
# 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ść.
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
| 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.
Materiały z warsztatów Data Science w zastosowaniach biznesowych - Warsztaty z wykorzystaniem programu R Uniwersytet Warszawski, Wydział Nauk Ekonomicznych
DataCamp.com Introduction to R
DataCamp.com Writing Efficient R Code
Eugene O’Loughlin tutorial
Materiały dr Bartosza Maćkiewicza:
Biecek, Przemysław, “Przewodnik po pakiecie R” http://www.biecek.pl/R/ https://cran.r-project.org/doc/contrib/Biecek-R-basics.pdf
Gągolewski, Marek, “Programowanie w języku R”, “Deep R Programming”, etc. https://deepr.gagolewski.com/index.html
Crawley, Michael J. The R book. John Wiley & Sons, 2012 https://onlinelibrary.wiley.com/doi/book/10.1002/9781118448908
Kabacoff, Robert. R in action: data analysis and graphics with R. Manning Publications Co., 2015 https://www.cs.uni.edu/~jacobson/4772/week11/R_in_Action.pdf
Fox, John, Michael Friendly, and Sanford Weisberg. “Hypothesis tests for multivariate linear models using the car package.” R J 5.1 (2013) https://www.researchgate.net/publication/285736465_Hypothesis_Tests_for_Multivariate_Linear_Models_Using_the_car_Package
Hothorn, Torsten, et al. “Package ‘multcomp’.” Simultaneous inference in general parametric models. Project for Statistical Computing, Vienna, Austria (2016) https://cran.r-project.org/web/packages/multcomp/vignettes/generalsiminf.pdf
claude.ai
chatgpt.com
Wickham, H., Çetinkaya-Rundel, M., Grolemund, G., R for Data Science (2nd ed.), rozdziały Data import i Spreadsheets: https://r4ds.hadley.nz/
Bryan, J., Hester, J., What They Forgot to Teach You About R (projekty, ścieżki, czysta sesja): https://rstats.wtf/
Bryan, J. (2017), Project-oriented workflow: https://www.tidyverse.org/blog/2017/12/workflow-vs-script/
Peng, R. (2015), stringsAsFactors: An unauthorized biography (blog Simply Statistics)
Ziemann, M., Eren, Y., El-Osta, A. (2016), Gene name errors are widespread in the scientific literature, Genome Biology, 17, 177. https://doi.org/10.1186/s13059-016-1044-7
Herndon, T., Ash, M., Pollin, R. (2014), Does high public debt consistently stifle economic growth? A critique of Reinhart and Rogoff, Cambridge Journal of Economics, 38(2)
DuckDB Labs, Database-like ops benchmark: https://duckdblabs.github.io/db-benchmark/
Horst, A. M., Hill, A. P., Gorman, K. B. (2020), palmerpenguins: https://allisonhorst.github.io/palmerpenguins/
Wilkinson, M. D. i in. (2016), The FAIR Guiding Principles for scientific data management and stewardship, Scientific Data, 3, 160018
pomarancz
szary
zielony
czerwony
niebieski