Ta część kursu ma za zadanie przypomnienie i uporządkowanie wiedzy z zakresu podstaw programowania w środowisku R. Omówione zostaną typy danych, typy obiektów operacje oraz zasady wykonywania pętli zgodnie ze filozofią języka R.
Wymagane biblioteki: brak
Wymagana wiedza: podstawy programowania
R obsługuje 4 typy proste. Typy proste mogą być nadawane zmiennym, które przechowują jeden typ danych: wektorom i ich rozwinięciom (macierzom. Zmienna to wektor o długości 1.
zmienna <- 17
zmienna
## [1] 17
wektor <- c(10,11,12,1,4,17,6)
Każdy typ może być konwertowany w górę, funkcją as*():
var <- TRUE
var
## [1] TRUE
as.integer(var)
## [1] 1
as.numeric(as.integer(var))
## [1] 1
as.character(as.numeric(as.integer(var)))
## [1] "1"
Konwersja w przeciwnym kierunku nie zawsze jest możliwa. Z reguły R radzi sobie z konwersją łańcuchów tekstowych na liczby, jeżeli łańcuch tekstowy jest tekstowym zapisem liczby. Konwersja typu numerycznego do liczby całkowitej jest możliwe ale kosztem ucięcia części ułamkowej. Jeżeli chcemy zastosować zasady zaokrąglania musimy użyć funkcji round()
. Konwersja wartości numerycznych do typu logicznego odbywa się wg zasady: 0 lub 0.0 to FALSE
każda inna wartość to TRUE
. Próba konwersji łańcucha tekstowego do typu logicznego zwróci wartość NA (Not Aviailable) za wyjątkiem literałów TRUE
i FALSE
, które będą zamienione na odpowiednie typy logiczne. Konwersja przypadkowego łańcucha tekstowego na liczbę również zakończy się zwróceniem NA - i komunikatem o błędzie.
liczba <- "17.1"
liczba
## [1] "17.1"
as.numeric(liczba)
## [1] 17.1
as.integer(liczba)
## [1] 17
liczba <- 17.6
as.integer(liczba)
## [1] 17
round(liczba)
## [1] 18
as.logical(0.0)
## [1] FALSE
as.logical("TRUE")
## [1] TRUE
as.logical("Tekst")
## [1] NA
as.numeric("tekst12") #zwróci komunikat o błędzie
## Warning: NAs introduced by coercion
## [1] NA
Niektóre operacje takie jak dzielnie czy dodawanie różnych typów prowadzą do niejawnej konwersji typu prostszego do bardziej pojemnego. Konwersje niejawną wymusi również połączenie elementu z danymi innego typu. Należy podkreślić, że nowo utworzony obiekt liczbowy jest klasy numeric, niezależnie od typu przypisanych danych.
wektor <- c(1,2,3,4)
is.integer(wektor)
## [1] FALSE
wektor <- as.integer(wektor)
is.integer(wektor)
## [1] TRUE
wektor <- wektor*1
is.integer(wektor)
## [1] FALSE
wektor <- c(wektor,1.0)
is.integer(wektor)
## [1] FALSE
wektor <- c(wektor,"2.0")
wektor
## [1] "1" "2" "3" "4" "1" "2.0"
R posiada specjalny typ wyliczeniowy reprezentowany w obliczeniach przez liczbę całkowitą ale przechowujący dowolną informację (najczęściej w formie łańcucha tekstowego). Dzięki kodowaniu informacji przy pomocy zmiennej całkowitej możemy znacznie przyspieszyć większość operacji. Aby zamienić faktor na reprezentujące je liczby całkowite używamy funkcji as.integer()
, natomiast funkcja as.character()
rozwija faktor do zwykłego typu znakowego. Więcej na temat typu faktor można przeczytać w dokumentacji programu (funkcja ```factor()````) oraz w podręczniku programowania w R.
wektor <- c("raz","dwa","trzy","cztery","raz","raz")
faktor <- factor(wektor)
str(faktor)
## Factor w/ 4 levels "cztery","dwa",..: 3 2 4 1 3 3
levels(faktor) #zwraca etykiety
## [1] "cztery" "dwa" "raz" "trzy"
?factor
as.integer(faktor)
## [1] 3 2 4 1 3 3
unclass(faktor) #zwraca kody
## [1] 3 2 4 1 3 3
## attr(,"levels")
## [1] "cztery" "dwa" "raz" "trzy"
as.character(faktor)
## [1] "raz" "dwa" "trzy" "cztery" "raz" "raz"
R korzysta z kilku typów specjalnych nie będących liczbą, znakiem ani wartości logiczną.
1/0 # inf
## [1] Inf
-1/0
## [1] -Inf
0/0 # not a number
## [1] NaN
wektor <- c(1,2,3)
wektor[4] #przekroczenie zakresu
## [1] NA
wektor <- NULL # obiekt pusty
wektor
## NULL
W R podstawowym typem obiektu jest wektor. Specjalna odmiana wektora o długości 1 określana jest terminem zmienna w celu kompatybilności z innymi językami programowania. Zmienną o długości 1 możemy utworzyć przez zwykłe przypisanie, wektor tworzymy przy pomocy funkcji c()
(lub na kilka innych sposobów, których tu nie będziemy omawiać) Cechą wektorów jest to że przechowują ciąg wartości jednego typu. Jeżeli wektor ma więcej niż 1 wymiar określany jest jako array, czyi wektor który posiada wymiary (1 lub więcej) oraz dodatkowe atrybuty. Jeżeli array posiada dwa wymiary jest tym samym czym matrix.
zmienna <- 3
is.vector(zmienna) #jest to wektor
## [1] TRUE
wektor <- 1:12
array(wektor,dim=c(3,4))
## [,1] [,2] [,3] [,4]
## [1,] 1 4 7 10
## [2,] 2 5 8 11
## [3,] 3 6 9 12
matrix(wektor,3,4)
## [,1] [,2] [,3] [,4]
## [1,] 1 4 7 10
## [2,] 2 5 8 11
## [3,] 3 6 9 12
matrix(wektor,3,4,byrow=TRUE) # wiersz po wierszu
## [,1] [,2] [,3] [,4]
## [1,] 1 2 3 4
## [2,] 5 6 7 8
## [3,] 9 10 11 12
dim(wektor) <- c(3,4)
is.vector(wektor) #teraz jest to macierz
## [1] FALSE
Dodawanie elementów do wektora odbywa się na kilka sposobów, z których najpopularniejsze to łączenie (konkatenacja) elementów (funkcja c()
), dołączanie elementów (funkcja append()
). Funkcja łączenia pozwala złączyć dwa lub więcej wektorów tworząc nowy obiekt. Funkcja append pozwala dodatkowo wskazać miejsce przyłączenia. Należy pamiętać że łączenie wektorów różnego typu spowoduje że zostanie wybrany bardziej ogólny typ danych.
wektor <- seq(1,10)
append(wektor,11:12)
## [1] 1 2 3 4 5 6 7 8 9 10 11 12
append(wektor,11:12,after=5)
## [1] 1 2 3 4 5 11 12 6 7 8 9 10
c(wektor,wektor)
## [1] 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10
c(wektor,"text") #uogólnienie do typu tekst
## [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
## [11] "text"
append(wektor,"text") #to samo
## [1] "1" "2" "3" "4" "5" "6" "7" "8" "9" "10"
## [11] "text"
W przypadku wektorów dwuwymiarowych czyli macierzy proces dołączania odbywa się po kolumnach lub po wierszach. W takiej sytuacji wielkość dołączanego obiektu musi odpowiadać długością ilości wierszy lub kolumn. Służą do tego funkcje rbind i cbind
dodające odpowiednio po wierszach i po kolumnach.
macierz <- matrix(seq(1:12),3,4,byrow=TRUE)
macierz
## [,1] [,2] [,3] [,4]
## [1,] 1 2 3 4
## [2,] 5 6 7 8
## [3,] 9 10 11 12
append(macierz,101:104) # likiwduje wymiary
## [1] 1 5 9 2 6 10 3 7 11 4 8 12 101 102 103 104
rbind(macierz,101:104) #dodaje wiersz
## [,1] [,2] [,3] [,4]
## [1,] 1 2 3 4
## [2,] 5 6 7 8
## [3,] 9 10 11 12
## [4,] 101 102 103 104
cbind(macierz,101:104) #zwróci ostrzeżenie o niezgodnej długości i skróci dodawany wektor
## Warning in cbind(macierz, 101:104): number of rows of result is not a
## multiple of vector length (arg 2)
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 101
## [2,] 5 6 7 8 102
## [3,] 9 10 11 12 103
cbind(macierz,101:103) #dodaje kolumnę
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 101
## [2,] 5 6 7 8 102
## [3,] 9 10 11 12 103
cbind(macierz,matrix(101:106,3,2,byrow=TRUE)) #doda dwie kolumny
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 1 2 3 4 101 102
## [2,] 5 6 7 8 103 104
## [3,] 9 10 11 12 105 106
cbind(macierz,matrix(101:106,2,3,byrow=TRUE)) #zgłosi błąd bo nie jest w stanie połączyć po kolumnach
## Error in cbind(macierz, matrix(101:106, 2, 3, byrow = TRUE)): number of rows of matrices must match (see arg 2)
Indeksowanie pozwala tworzyć podzbiory większych zbiorów danych. R pozwala indeksować obiekty w sposób ciągły (od - do) lub tylko wybrane elementy. Wybrane elementy mogą się powtarzać. Elementy wektora/macierzy można wybierać również na podstawie obiektu zawierającego zmienne logiczne (TRUE/FALSE). Takie obiekty powstają najczęściej w wyniku testu logicznego (o czym szerzej w następnej części) Wektory i macierze indeksowane są od 1 (wiele języków programowania indeksuje od zera). Ujemne indeksy wybierają wszystkie za wyjątkiem wskazanych. Sekwencje tworzymy funkcją seq()
lub operatorem :
. Ujemnych indeksów nie można mieszać z dodatkami. Można wskazać również wartości spoza zakresu, zostanie wtedy zwrócona wartość NA (not available). Jako indeksy można też użyć wartości numerycznych (zmiennoprzecinkowych). Zostaną one zamienione na odpowiadające im wartości całkowite poprzez usunięcie części ułamkowej.
wektor <- seq(10,20)
wektor
## [1] 10 11 12 13 14 15 16 17 18 19 20
wektor[] #wybiera wszystkie elementy
## [1] 10 11 12 13 14 15 16 17 18 19 20
wektor[1:5] # wybiera pierwsze 5
## [1] 10 11 12 13 14
wektor[c(1,1,1,2,2,2)] # powtarzające się elementy
## [1] 10 10 10 11 11 11
wektor[c(1,3:5)] # pierwszy, trzeci do piątego.
## [1] 10 12 13 14
wektor[-1] #wszystkie za wyjątkiem pierwszego
## [1] 11 12 13 14 15 16 17 18 19 20
wektor[-c(1:5)] #wszystkie za wyjątkiem pierwszych pięciu
## [1] 15 16 17 18 19 20
wektor[c(TRUE,TRUE,FALSE,FALSE,FALSE,TRUE,TRUE,FALSE,TRUE,TRUE)]
## [1] 10 11 15 16 18 19 20
#selekcja przy pomocy wektora logicznego o takiej samej długości
wektor[c(1.1,2.3,3.6,4.1)] #wartośc zmiennoprzecinkowe, 3.6 jest zamienione na 3
## [1] 10 11 12 13
wektor[round(c(1.1,2.3,3.6,4.1))] # a tu powtórzy się czwarty element
## [1] 10 11 13 13
Macierze są indeksowane przy pomocy dwóch indeksów. Pierwszy oznacza wiersz(e) drugi kolumnę(y): macierz[row(s),col(s)]
. Podobnie jak w przypadku wektorów można stosować zarówno pojedyncze liczby, przedziały ciągłe i nieciągłe oraz wektory logiczne. Można stosować ujemne wartości w celu eliminowania niechcianych elementów. Jeżeli chcemy zwrócić wszystkie elementy dla jednego z wymiarów zostawiamy pole danego wymiaru puste.
macierz <- matrix(1:20,4,5,byrow=TRUE)
macierz
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 5
## [2,] 6 7 8 9 10
## [3,] 11 12 13 14 15
## [4,] 16 17 18 19 20
macierz[1:2,3:4]
## [,1] [,2]
## [1,] 3 4
## [2,] 8 9
macierz[,2] # druga kolumna w całości
## [1] 2 7 12 17
macierz[2:4,] #wiersze od 2 do 4, wszystkie kolumny
## [,1] [,2] [,3] [,4] [,5]
## [1,] 6 7 8 9 10
## [2,] 11 12 13 14 15
## [3,] 16 17 18 19 20
macierz[-1,] #to samo
## [,1] [,2] [,3] [,4] [,5]
## [1,] 6 7 8 9 10
## [2,] 11 12 13 14 15
## [3,] 16 17 18 19 20
diag(macierz) #przekątna
## [1] 1 7 13 19
Indeksowanie pozwala na tworzenie podzbiorów, których dotychczasowa zawartość zostanie zastąpiona nową zawartością.
macierz <- matrix(1:20,4,5,byrow=TRUE)
macierz
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 5
## [2,] 6 7 8 9 10
## [3,] 11 12 13 14 15
## [4,] 16 17 18 19 20
macierz[2:3,3:4] <- NA
macierz
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 5
## [2,] 6 7 NA NA 10
## [3,] 11 12 NA NA 15
## [4,] 16 17 18 19 20
macierz[is.na(macierz)] <- 0 #indeksowanie przez operację logiczną
macierz
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 5
## [2,] 6 7 0 0 10
## [3,] 11 12 0 0 15
## [4,] 16 17 18 19 20
macierz[2:3,3:4] <- matrix(seq(50,65,by=5),2,2)
macierz
## [,1] [,2] [,3] [,4] [,5]
## [1,] 1 2 3 4 5
## [2,] 6 7 50 60 10
## [3,] 11 12 55 65 15
## [4,] 16 17 18 19 20
Jeżeli chcemy przechowywać w jednym obiekcie różne dane różnego typu R oferuje typ obiektu o bardzo dużych możliwościach czyli listy. Lista to zbiór obiektów dowolnego typu. Dostęp do elementów listy odbywa się poprzez indeksowanie podobne do wektorów lub poprzez znak $ i nazwę obiektu jeżeli są nazwane. Jeżeli elementy listy nie są nazwane to możemy dostać się do nich przez index w podwójnych nawiasach. Pojedyncze nawiasy zwrócą nam sub-listę o indeksach użytych w selekcji
lista <- list(a=1:3,b=4:6,c("raz","dwa","trzy"))
lista[[1]] # pierwszy element listy
## [1] 1 2 3
lista$a #element a (też pierwszy)
## [1] 1 2 3
lista[1] #pierwszy element jako lista
## $a
## [1] 1 2 3
lista[1:2] #dwa pierwsze elementy
## $a
## [1] 1 2 3
##
## $b
## [1] 4 5 6
lista[c(1,3)] #lista z pierwszego i trzeciego elementu listy
## $a
## [1] 1 2 3
##
## [[2]]
## [1] "raz" "dwa" "trzy"
Listy mogą być zagnieżdżone. Dowolny element listy może być listą (itp.)
lista_zag <- list(
l=list(1,2,c("a","b")),
el=1,
vect=1:4)
lista_zag
## $l
## $l[[1]]
## [1] 1
##
## $l[[2]]
## [1] 2
##
## $l[[3]]
## [1] "a" "b"
##
##
## $el
## [1] 1
##
## $vect
## [1] 1 2 3 4
lista_zag[[1]][[2]] #wybór drugiego elementu listy l
## [1] 2
lista_zag$l[[2]] # to samo dostęp do elementu listy l przez nazwę
## [1] 2
Popularne ramki danych (data.frame) są w praktyce specjalną odmianą list, gdzie poszczególne elementy muszą być jednakowej długości. Stosowane są w celu zgodności z naturalnym dla człowieka myśleniem tabelarycznym.
x <- 1:5
y <- seq(1.1,1.5,by=0.1)
op <- rep("n",5)
df <- data.frame(x,y,op)
str(df)
## 'data.frame': 5 obs. of 3 variables:
## $ x : int 1 2 3 4 5
## $ y : num 1.1 1.2 1.3 1.4 1.5
## $ op: Factor w/ 1 level "n": 1 1 1 1 1
df$x #wektor wartości
## [1] 1 2 3 4 5
df[[1]] #tak jak lista
## [1] 1 2 3 4 5
df[1] #tak jak lista,
## x
## 1 1
## 2 2
## 3 3
## 4 4
## 5 5
str(df[1]) #yyle że będzie to data.frame
## 'data.frame': 5 obs. of 1 variable:
## $ x: int 1 2 3 4 5
Ramki danych pozwalają na stosowanie poleceń rbind i cbind
, pod warunkiem zgodności nie tylko długości ale i typów danych.
rbind(df,list(6,1.6,"k")) #NA w trzeciej kolumnie
## Warning in `[<-.factor`(`*tmp*`, ri, value = "k"): invalid factor level, NA
## generated
## x y op
## 1 1 1.1 n
## 2 2 1.2 n
## 3 3 1.3 n
## 4 4 1.4 n
## 5 5 1.5 n
## 6 6 1.6 <NA>
df$op <- as.character(df$op) #konwersja na typ znakowy
rbind(df,list(6,1.6,"k")) #teraz działa
## x y op
## 1 1 1.1 n
## 2 2 1.2 n
## 3 3 1.3 n
## 4 4 1.4 n
## 5 5 1.5 n
## 6 6 1.6 k
cbind(df,z=(10:14)) #dodana nowa kolumna z
## x y op z
## 1 1 1.1 n 10
## 2 2 1.2 n 11
## 3 3 1.3 n 12
## 4 4 1.4 n 13
## 5 5 1.5 n 14
df$p <- (10:14) #utworzenie nowej kolumny - podejście niestandardowe
df
## x y op p
## 1 1 1.1 n 10
## 2 2 1.2 n 11
## 3 3 1.3 n 12
## 4 4 1.4 n 13
## 5 5 1.5 n 14
Każdy obiekt R należy do jakieś klasy. Zwykłe wektory i macierze należą do klas wbudowanych.
zmienna <- 3
class(zmienna) #zwraca typ w przypadku wektora
## [1] "numeric"
wektor=seq(2,13)
class(wektor)
## [1] "integer"
dim(wektor) <- c(3,4) #dodanie wymiarów zmienia rodzaj klasy
class(wektor)
## [1] "matrix"
macierz <- matrix(seq(1:12),3,4,byrow=TRUE)
class(macierz)
## [1] "matrix"
R pozwala definiować własne klasy (nie będziemy się zajmować na tym etapie). Z punktu widzenia dalszej części kursu istotne jest że klasy w R zorganizowane są w sloty (slots) oznaczone symbolem @
. Każdy slot może posiadać dowolną zawartość, której typ jest jednak z góry określony. Mogą to być obiekty proste (wektory, macierze) należące do zdefiniowanego typu danych, listy, ramki danych ale też inne klasy. Dane geo-przestrzenne w R są zdefiniowane w postaci złożonych klas, które będą omawiane w dalszych częściach kursu.
UWAGA W tej części nie należy mylić pojęć obiekt wektorowy (ciąg danych) z obiektem geoprzestrzennym
Operacje na zmiennych (czyli wektorach o długości 1) wykonuje się w sposób prosty i intuicyjny. Na obiektach o większej długości w tym wielowymiarowych operacje wykonuje się w taki sam sposób jak w przypadku prostej zmiennej, co jest intuicyjne do momentu w którym do obiektu wielowymiarowego dodajemy zmienną. W takiej sytuacji pojedynczy obiekt dodawany jest do wszystkich elementów obiektu wielowymiarowego. Bardzo ułatwia to obliczenia matematyczne w R szczególnie rachunek macierzowy bez konieczności stosowania pętli. W językach skryptowych ze względu na konieczność każdorazowego określania typu pętli wykonywane są bardzo wolno. Z tego powodu operacje obliczeniowe na typach prostych są wykonywane bezpośrednio.
zmienna <- 7
macierz <- matrix(seq(1,12),3,4,byrow=TRUE)
zmienna + 5
## [1] 12
macierz + 10
## [,1] [,2] [,3] [,4]
## [1,] 11 12 13 14
## [2,] 15 16 17 18
## [3,] 19 20 21 22
macierz + zmienna
## [,1] [,2] [,3] [,4]
## [1,] 8 9 10 11
## [2,] 12 13 14 15
## [3,] 16 17 18 19
Sytuacja komplikuje się jeżeli chcemy do siebie dodać obiekty o długości większej niż 1. Jeżeli elementy są jednakowej długości, to operacja będzie wykonywana element po elemencie. Jeżeli elementy będą różnej długości, element krótszy zostanie cyklicznie powielony do długości elementu dłuższego. Ze względu na nie-intuicyjność należy unikać tego typu operacji.
wektor1=1:5
wektor2=6:10
wektor1+wektor2
## [1] 7 9 11 13 15
wektor3 <- 1:2 #wektor dwulementowy
wektor1 + wektor3 #zostanie wygenerowane ostrzeżenie o powieleniu danych
## Warning in wektor1 + wektor3: longer object length is not a multiple of
## shorter object length
## [1] 2 4 4 6 6
wektor1 + wektor3[c(1,2,1,2,1)] #powielenie wykonane ręcznie brak ostrzeżenia
## [1] 2 4 4 6 6
macierz <- matrix(seq(1,12),3,4,byrow=TRUE)
macierz + wektor3 #brak ostrzeżenia w przypadku obiektów wielowymiarowych
## [,1] [,2] [,3] [,4]
## [1,] 2 4 4 6
## [2,] 7 7 9 9
## [3,] 10 12 12 14
R pozwala na stosowanie standardowych operatorów matematycznych i wbudowanych funkcji matematycznych. Jak pokazano powyżej operatory matematyczne działają zgodnie z zasadami matematycznymi.
w1=1:5
w2=6:10
w1+w2
## [1] 7 9 11 13 15
w1/w2
## [1] 0.1666667 0.2857143 0.3750000 0.4444444 0.5000000
w1*w2
## [1] 6 14 24 36 50
w1/w2
## [1] 0.1666667 0.2857143 0.3750000 0.4444444 0.5000000
w2%/%w1 #dzielenie całkowite
## [1] 6 3 2 2 2
w2%%2 #dzielenie modulo
## [1] 0 1 0 1 0
w1^2 #podnoszenie do potęgi
## [1] 1 4 9 16 25
w2^(1/w1) #pierwiastek dowolnego stopnia
## [1] 6.000000 2.645751 2.000000 1.732051 1.584893
-w1 #odwrócenie wartości
## [1] -1 -2 -3 -4 -5
Wbudowane funkcje matematyczne zwracają wektory o długości takiej jak przekazany wektor lub macierz danych. Jeżeli funkcja nie może być wykonana zwracana jest NA.
w1 <- c(-2,3,-1,5,6,-3)
sqrt(w1)
## Warning in sqrt(w1): NaNs produced
## [1] NaN 1.732051 NaN 2.236068 2.449490 NaN
abs(w1)
## [1] 2 3 1 5 6 3
w1 <- w1+runif(length(w1))
w1
## [1] -1.3945861 3.7969268 -0.4292477 5.6780926 6.6619021 -2.3661828
round(w1)
## [1] -1 4 0 6 7 -2
stopnie <- c(30,60,90)
sin(stopnie) #wartości muszą być konwertowane na radiany
## [1] -0.9880316 -0.3048106 0.8939967
sin(stopnie*(pi/180)) #brak funkcji do konwersji stopni na radiany
## [1] 0.5000000 0.8660254 1.0000000
Istnieje też grupa funkcji, które pobierają wektor/macierz a zwracają pojedynczą wartość. Takie funkcje nazywamy podsumowującymi.
sum(w1)
## [1] 11.9469
min(w1)
## [1] -2.366183
range(w1) #zwraca wektor o długości 2 (min - max)
## [1] -2.366183 6.661902
mean(w1)
## [1] 1.991151
sd(w1)
## [1] 3.87242
Operacje logiczne to takie operacje których wynikiem jest prawda lub fałsz. W R wynikiem operacji logicznej może być pojedyncza zmienna, wektor lub macierz. Operacje logiczne to różne operacje porównania oraz wyniki działania niektórych funkcji.
wektor <- seq(1,6)
wektor<3
## [1] TRUE TRUE FALSE FALSE FALSE FALSE
wektor==5 #do porównania używamy podwójnego ==
## [1] FALSE FALSE FALSE FALSE TRUE FALSE
is.vector(wektor) #funkcja testująca czy obiekt jest klasy wektor
## [1] TRUE
Operacje porównania można ze sobą łączyć przy pomocy operatorów logicznych: AND &, OR | lub NOT ! element po elemencie:
wektor1 <- seq(1,6)
wektor2 <- seq(11,16)
wektor1<3 | wektor1>4
## [1] TRUE TRUE FALSE FALSE TRUE TRUE
wektor1>=3 & wektor1<=4
## [1] FALSE FALSE TRUE TRUE FALSE FALSE
wektor1<2 & wektor2<15 # spełnienie dwóch warunków w osobnych obiektach jednocześnie, operacja element po elemencie
## [1] TRUE FALSE FALSE FALSE FALSE FALSE
!(wektor1<2 & wektor2<15) # negacja poprzedniej operacji
## [1] FALSE TRUE TRUE TRUE TRUE TRUE
lub dla całego zbioru danych. W takiej sytuacji & zastępujemy && a | przez ||.
any(wektor1<2) #sprawdza czy dowolny element większy od 1
## [1] TRUE
all(wektor1<2) #sprawdza wszystkie elementy większy od 1
## [1] FALSE
wektor1<2 && wektor2<2 # wektor2 nie zawiera elementów <2 bo operator AND
## [1] FALSE
wektor1<2 || wektor2<2 # wektor1 zawiera elementy< 2 i to wystarczy bo operator OR
## [1] TRUE
Operacje logiczne można też stosować dla macierzy.
macierz <- matrix(seq(1,12),3,4,byrow=TRUE)
macierz%%2==0 #operator modulo zwraca resztę z dzielenia jeżeli x%%2 == 0 to jest to liczba nieparzysta
## [,1] [,2] [,3] [,4]
## [1,] FALSE TRUE FALSE TRUE
## [2,] FALSE TRUE FALSE TRUE
## [3,] FALSE TRUE FALSE TRUE
Operacje logiczne dla wartości specjalnych (NA, NaN) muszą być realizowane poprzez funkcje a nie zwykłe porównanie:
x <- 1/0
x==Inf #działa bo Inf jest liczbą
## [1] TRUE
x <- 0/0
x == NaN # nie działa zwraca wartość nieokreśloną
## [1] NA
is.nan(x) #funkcja działa i zwraca TRUE
## [1] TRUE
is.na(x) # na jest ogólniejszą postacią, NaN zawiera się w NA
## [1] TRUE
x <- NA
is.nan(x) #ale NA nie zawiera się w NaN
## [1] FALSE
x <- NULL
x == NULL # jak wyżej, nie działa, bo x to wartość pusta o długości 0
## logical(0)
is.null(x) #działa ale nie nie należy testować w tym wypadu is.na NA !=NULL
## [1] TRUE
Umiejętność pisania i stosowania własnych funkcji jest esencją programowania. Funkcja składa się z nagłówka i ciała funkcji. Nagłówek zawiera atrybuty i mogą być przypisane do obiektu o dowolnej nazwie. Funkcja zwraca wartość znajdującą się w funkcji! return
. Funkcja może być przypisana do dowolnej nazwy
nowa_fukcja <- function(x) #atrybut x
{
#ciało funkcji
return(x) #funkcja nic nie robi zwraca przekazaną zawartość
}
ta_sama_funkcja <- nowa_fukcja
ta_sama_funkcja
## function(x) #atrybut x
## {
## #ciało funkcji
## return(x) #funkcja nic nie robi zwraca przekazaną zawartość
## }
Funkcja może mieć kilka rodzajów atrybutów:
funkcja <- function(wartosc_wymagana,wartosc_domyslna=2)
{
return(wartosc_wymagana^wartosc_domyslna)
}
wektor <- 2:5
funkcja(wektor) #podniesienie do kwadratu 2 to wartość domyślna
## [1] 4 9 16 25
funkcja(wektor,3) #podniesienie do sześcianu
## [1] 8 27 64 125
...
pozwalający na przekazanie dowolnych atrybutów. Stosuje się między innymi jeżeli funkcja posiada zagnieżdżoną inną funkcję zawierającą parametry domyślne i chcemy pozwolić na modyfikację tych atrybutówfunkcja <- function(x,...)
{
plot(x,...)
}
wektor <- 1:5
funkcja(wektor) #wyświetlenie wektora z wartościami domyślnymi
funkcja(wektor,pch=20,main="Tytuł") #pozwala na zmianę parametru wykresu, który nie został uwzględniony w ciele funcji funcja
funkcja <- function(x,FUN)
{
FUN(x/3)
}
wektor <- 1:5
funkcja(wektor,sum) #zwraca sumę (jedną liczbę)
## [1] 5
funkcja(wektor,sqrt) #zwraca wektor pierwiastków
## [1] 0.5773503 0.8164966 1.0000000 1.1547005 1.2909944
R jak każdy język programowania posiada standardowe instrukcje sterujące. Ze względu na interpolowany charakter języka bezpośrednie stosowanie instrukcji jest bardzo powolne ze względu na narzut każdorazowego interpretowania funkcji. Z tego powodu R podobnie jak inne języki skryptowe stosuje specjalne rozwiązania pozwalające unikać kolejnej interpretacji kodu w każdej iteracji. W tej części porównamy instrukcje sterujące z rozwiązaniami właściwymi dla języka R (funkcje apply
i lapply
). Wiele z tych zagadnień będzie dyskutowana w kolejnych częściach kursu.
R jak każdy język programowania posiada instrukcje warunkowe. Podstawową instrukcją warunkową jest konstrukcja if (test) {commands} else {commands}. W prostych przypadkach konstrukcja if - else może być zastąpiona funkcją ifelse(_condition_,_WHAT IF TRUE_,_WHAT IF FALSE_)
. Struktury if-else muszą być stosowane jeżeli w obrębie instrukcji warunkowej wykonywane jest więcej niż jedna funkcja.
a <- 10
if(a>5) {
print("większe")
} else {
print("nie większe")
}
## [1] "większe"
#To samo przy pomocy funkcji
ifelse(a>5,"większe","nie większe")
## [1] "większe"
Język R nie posiada instrukcji switch, zastępuje je funkcją switch(), gdzie pierwszy argument to test, wskazujący, który kolejny element funkcji ma być wykonany/zwrócony
x <- 2
switch(x,"banan",sum(1:10),x/3)
## [1] 55
x <- 3
switch(x,"banan",sum(1:10),x/3)
## [1] 1
R - podobnie do innych języków posiada dwie instrukcje powtarzania kodu. Instrukcję repeat
która pozwala na wykonywanie kodu do jego przerwania. Instrukcję repeat
stosuje się gdy nie jest znana docelowa ilość powtórzeń (iteracji). Przerwanie kodu następuje instrukcją break
. Jeżeli liczba powtórzeń jest znana należy stosować instrukcję for
, która wykonuje planowany ciąg instrukcji tyle razy ile określono w nagłówku instrukcji.
# Stosujemy wartość losową nie wiemy kiedy założona wartość zostanie przekroczona
warunek_zatrzymania <- 200
zmienna <- 0
k <- 0 #ilość iteracji
repeat {
zmienna <- zmienna+runif(1,2,3) #zmienna losowa z przedziału 2:3
k <- k+1
if(zmienna>warunek_zatrzymania) break
}
zmienna
## [1] 202.3572
print(paste("wykonano",k,"iteracji"))
## [1] "wykonano 81 iteracji"
zmienna <- 0
for( i in 1:100) {
zmienna <- zmienna + runif(1,2,3) #1 wartość w przedziale od 2 do 3
}
zmienna
## [1] 247.4308
Bardzo często stosowanie pętli iteracyjnej nie jest konieczne. R stosuje tzw. wektoryzację, co oznacza, że powyższa pętla może być zastąpiona funkcjami/oeracjami języka R. wszędzie gdzie to możliwe należy zastępować pętle funkcjami. Na jednowymiarowych wektorach operacje i funkcje matematyczne są wystarczające w większości sytuacji.
sum(runif(100,2,3))
## [1] 253.0124
W przypadku macierzy, gdy mamy dwa wymiary do wykonania obliczeń po wierszach lub po kolumnach (wzdłuż jednego wymiaru) musimy zastosować funkcję apply()
, która jako argument pobiera zbiór danych oraz funkcję do zastosowania po jednym z wymiarów (margin) macierzy. Jako funkcję możemy użyć własnej formuły, zarówno jako wcześniej zdefiniowanej funkcji albo jako tzw funkcji anonimowej.m
macierz <- matrix(runif(30),5,6)
macierz
## [,1] [,2] [,3] [,4] [,5] [,6]
## [1,] 0.5087760 0.65913363 0.5819243 0.32972702 0.5132909 0.7585150
## [2,] 0.6681041 0.08259741 0.8981893 0.09819590 0.6237705 0.1516391
## [3,] 0.4676197 0.44624640 0.7659075 0.81728235 0.3396401 0.3502103
## [4,] 0.7693412 0.18773144 0.2242698 0.74666882 0.6221390 0.7273667
## [5,] 0.2276218 0.56772012 0.8884601 0.03067149 0.6068156 0.2809995
apply(macierz,1,sum) # suma wierszy
## [1] 3.351367 2.522496 3.186906 3.277517 2.602289
apply(macierz,2,sum) # suma kolumn
## [1] 2.641463 1.943429 3.358751 2.022546 2.705656 2.268731
apply(macierz,2,function(x) sum(x)/sd(x)) #funkcja anonimowa
## [1] 12.733946 7.891690 11.961056 5.574111 22.262992 8.290010
moja_funkcja <- function(x) {
sum(x)/sd(x)
}
apply(macierz,2,moja_funkcja) #zdefiniowana wcześniej funkcja
## [1] 12.733946 7.891690 11.961056 5.574111 22.262992 8.290010
Podobna konstrukcja jest możliwa w przypadku list. Służy do tego funkcja sapply()
. Jeżeli wynikiem funkcji apply
i sapply
jest pojedyncza wartość zdefiniowanego typu (np liczba) to funkcje te zwracają wektor. W przeciwnym wypadku, jeżeli jest to bardziej złożony obiekt zwracają listę.
lista <- list(rnorm(19),rnorm(7),rnorm(15),rnorm(4))
#lista czterech wektorów o roznej długości
lista
## [[1]]
## [1] 0.5429152 -1.7691940 -0.5475994 -1.4007908 -0.5805110 0.7949317
## [7] 1.5135407 -1.3495925 -0.8119418 -0.1314265 1.2343527 -0.5637160
## [13] 0.7648420 0.2060117 2.8912299 -0.6507195 0.7799056 3.4776331
## [19] 0.1436976
##
## [[2]]
## [1] -0.98953308 -0.48935801 -0.27748331 -0.18605319 0.11338289 -0.09524341
## [7] -0.43134437
##
## [[3]]
## [1] -0.5242357 0.8665145 -1.8906807 -1.7394548 -2.4244135 -0.4071759
## [7] 0.5810728 1.2983807 -1.6527943 -2.9401811 0.8701119 0.6179744
## [13] -0.4135684 2.5454674 0.4480170
##
## [[4]]
## [1] -0.8595889 0.2531847 1.4747596 1.6001803
lapply(lista,mean) #zwraca listę o długości 4,
## [[1]]
## [1] 0.2391352
##
## [[2]]
## [1] -0.3365189
##
## [[3]]
## [1] -0.3176644
##
## [[4]]
## [1] 0.6171339
sapply(lista,mean) #zwraca wektor o długości 4, wersja uproszczona simplified-apply
## [1] 0.2391352 -0.3365189 -0.3176644 0.6171339
Istnieje cała grupa funkcji *apply
, których stosowanie jest ograniczone i wykracza poza zakres kursu. Po szczegóły odsyłam do dokumentacji języka R.
W czasie wykonywania kodu, może dojść do sytuacji, gdy z jakiegoś powodu (ograniczeń matematyki) operacja nie będzie mogła być wykonana. W takiej sytuacji, program najczęściej przestaje działać, zgłaszając błąd. Wiele wbudowanych funkcji R posiada własną obsługę wyjątków, niemniej jednak jeżeli planujemy własne rozwiązania, musimy zapewnić działanie kodu, w sytuacji, gdy wyjątek zajdzie. Przykładem może być przetworzenie listy (nie wektora) zawierającego kilka wartości i znaków. Takie coś może się pojawić w sytuacji np. wczytania danych zewnętrznych. Wykonamy wyliczenie logarytmu na liście zawierającej oprócz wartości dodatnich również tekst i wartości ujemne. Użyjemy do tego funkcji sapply(lista,log)
, która jest odpowiednikiem: for (i in length(lista)) result=append(result,log(i))
lista=list(1,2,3,4,10,-1,1e4, "1p",3,5) #lista zawiera wartości ujemne i tekst, będący efektem np. nieprawidłowego wczytania danych
sapply(lista,log) # zgłasza błąd
## Warning in FUN(X[[i]], ...): NaNs produced
## Error in FUN(X[[i]], ...): non-numeric argument to mathematical function
W tym celu napiszemy funkcję przychwytującą takie błędy, i zwracające wartość NA. Służy do tego funkcja tryCatch()
. Działa ona w następujący sposób: jeżeli ewaluacja wyrażenia (czyli tu log(i)
) przebiega prawidłowo, nic się nie dzieje, jeżeli jednak, został zwrócone ostrzeżenie (pętla nie została przerwana) lub błąd (pętla została przerwana) to można to przechwycić i zastąpić innym działaniem.
saveLog <- function(x) {
tryCatch(log(x), #to staramy się zrobić
warning=(function(w) -1), #ostrzeżenie, pewnie wartość ujemna, zwróci -1
error= (function(e) NaN) #błąd, pewnie nie-liczba, zwróci Not-a-Number
) #koniec bloku try-catch
} #koniec funkcji
sapply(lista,saveLog) #działa!
## [1] 0.0000000 0.6931472 1.0986123 1.3862944 2.3025851 -1.0000000
## [7] 9.2103404 NaN 1.0986123 1.6094379