Programowanie w R

Dariusz Brzeziński

Część II

Agenda

  • Filtrowanie danych
  • Instrukcje sterujące
  • Funkcje
  • Split-Apply-Combine
  • Debugowanie

Slajdy oparte o materiały stworzone przez Rogera Penga.

Filtrowanie

W R są trzy podstawowe operatory, które można wykorzystać do filtrowania obiektów:

  • [ służy do filtrowania wektorów,
  • [[ pozwala wybierać elementy listy lub data frame'a,
  • $ pozwala odczytywać elementy listy lub data frame'a po nazwie.

Filtrowanie wektorów

> x <- c("a", "b", "c", "c", "d", "a")
> x[1]
[1] "a"
> x[2]
[1] "b"
> x[1:4]
[1] "a" "b" "c" "c"
> x[x > "a"]
[1] "b" "c" "c" "d"
> u <- x > "a"
> u
[1] FALSE TRUE TRUE TRUE TRUE FALSE 
> x[u]
[1] "b" "c" "c" "d"

Filtrowanie macierzy

Macierze (i data frame'y) można filtrować za pomocą indeksów (i,j).

> x <- matrix(1:6, 2, 3)
> x[1, 2]
[1] 3

Gdy pominiemy indeks, brane są wszystkie wartości z danego wymiaru.

> x[1, ]
[1] 1 3 5
> x[, 2]
[1] 3 4

Filtrowanie list

> x <- list(foo = 1:4, bar = 0.6)
> x[1]
$foo
[1] 1 2 3 4

> x[[1]]
[1] 1 2 3 4

> x$bar
[1] 0.6
> x[["bar"]]
[1] 0.6
> x["bar"]
$bar
[1] 0.6

Filtrowanie list

> x <- list(foo = 1:4, bar = 0.6, baz = "halo")
> x[c(1, 3)]
$foo
[1] 1 2 3 4

$baz
[1] "halo"

Filtrowanie list

Operator [[ może korzystać ze zmiennych, operator $ przyjmuje tylko literały.

> x <- list(foo = 1:4, bar = 0.6, baz = "halo")
> name <- "foo"
> x[[name]]  ## indeks ‘foo’ w zmiennej
[1] 1 2 3 4
> x$name     ## nie ma indeksu o nazwie ‘name’!
NULL
> x$foo
[1] 1 2 3 4  ## jest element o nazwie ‘foo’

Częściowe dopasowanie

Choć nie jest to polecane, operatory [[ i $ mogą wykorzystać częściowe dopasowanie. Warunek: dopasowanie musi być jednoznaczne.

> x <- list(aardvark = 1:5)
> x$a
[1] 1 2 3 4 5
> x[["a"]]
NULL
> x[["a", exact = FALSE]]
[1] 1 2 3 4 5

Usuwanie brakujących wartości

Często wykonywanym filtrowaniem jest usuwanie brakujących wartości (NA).

> x <- c(1, 2, NA, 4, NA, 5)
> bad <- is.na(x)
> x[!bad]
[1] 1 2 4 5

Usuwanie brakujących wartości

A co jeśli mamy wiele obiektów i chcemy zostawić tylko podzbiór bez żadnych brakujących wartości w żadnym z obiektów?

> x <- c(1, 2, NA, 4, NA, 5)
> y <- c("a", "b", NA, "d", NA, "f")
> good <- complete.cases(x, y)
> good
[1]  TRUE  TRUE FALSE  TRUE FALSE  TRUE
> x[good]
[1] 1 2 4 5
> y[good]
[1] "a" "b" "d" "f"

Usuwanie brakujących wartości

> airquality[1:6, ]
  Ozone Solar.R Wind Temp Month Day
1    41     190  7.4   67     5   1
2    36     118  8.0   72     5   2
3    12     149 12.6   74     5   3
4    18     313 11.5   62     5   4
5    NA     NA  14.3   56     5   5
6    28     NA  14.9   66     5   6
> good <- complete.cases(airquality)
> airquality[good, ][1:6, ]
  Ozone Solar.R Wind Temp Month Day
1    41     190  7.4   67     5   1
2    36     118  8.0   72     5   2
3    12     149 12.6   74     5   3
4    18     313 11.5   62     5   4
7    23     299  8.6   65     5   7

Instrukcje sterujące

R zawiera wszystkie podstawowe instrukcje sterujące jakich spodziewamy się po języku programowania:

  • if, else: warunki
  • for: pętle
  • while: więcej pętli
  • repeat: jeszcze więcej pętli
  • break: instrukcje przerywania pętli
  • next: instrukcje kontynuowania pętli
  • return: zwracanie wartości

if

To jest poprawny if-else:

if(x > 3) {
    y <- 10
} else {
    y <- 0
}

To też:

y <- if(x > 3) {
    10
} else { 
    0
}

for

Trzy równoważne pętle for:

x <- c("a", "b", "c", "d")

for(i in 1:4) {
    print(x[i])
}

for(i in seq_along(x)) {
    print(x[i])
}

for(letter in x) {
    print(letter)
}

while

Pętle while zaczynają się od sprawdzenia warunku i kolejne wykonania pętli ponownie sprawdzają czy pętla ma być dalej wykonywana.

count <- 0
while(count < 10) {
    print(count)
    count <- count + 1
}

Jak to przy pętlach while, trzeba uważać na warunek zatrzymania.

repeat

repeat to odpowiednik pętli do-while. Jest to chyba najrzadziej wykorzystywana pętla, bo wymaga jawnego przerwania instrukcją break.

x0 <- 1
tol <- 1e-8

repeat {
    x1 <- computeEstimate()

    if(abs(x1 - x0) < tol) {
        break
    } else {
        x0 <- x1
    } 
}

next, return

next pozwala pominąć część kroku pętli (jest to odpowiednik instrukcji continue)

for(i in 1:100) {
    if(i <= 20) {
        ## Pomiń pierwsze 20 iteracji
        next 
    }
    ## Zrób coś później
}

return() zwraca wartość i wychodzi z funkcji… tak… return() w R jest funkcją…

Funkcje

Funkcje w R tworzy się za pomocą dyrektywy function() i przypisuje do zmiennej jak każdy inny obiekt. W praktyce funkcje w R są obiektami typu “function”.

f <- function(<argumenty>) {
    ## Powtarzalne zadanie
}

Funkcje w R:

  • mogą być przekazywane jako argumenty innych funkcji,
  • mogą być zagnieżdżane,
  • zwracają wynik ostatniego wywołanego wyrażenia.

Argumenty funkcji

Funkcje mają nazwane argumenty, które mogą mieć wartości domyślne. Argumenty zdefiniowane w sygnaturze funkcji to argumenty formalne.

Nie każde wywołanie musi podawać wartości wszystkich argumentów formalnych - mogą być brakujące lub mieć wartość domyślną.

Dopasowywanie argumentów

Argumenty mogą być dopasowane przez pozycję lub nazwę. Wszystkie poniższe wywołania funkcji sd są równoważne.

> mydata <- rnorm(100)
> sd(mydata)
> sd(x = mydata)
> sd(x = mydata, na.rm = FALSE)
> sd(mydata, na.rm = FALSE)
> sd(na.rm = FALSE, x = mydata)
> sd(na.rm = FALSE, mydata)

Choć jest to dopuszczalne, nie zaleca się mieszania kolejności parametrów.

Dopasowywanie argumentów

Jak wiele dopasowań w R, również dopasowanie argumentów może być częściowe. Kolejne czynności przy określaniu dopasowania to:

  1. Sprawdź dokładne dopasowanie przez nazwę
  2. Sprawdź częściowe dopasowanie
  3. Określ dopasowanie przez pozycję

Definiowanie funkcji

f <- function(a, b = 1, c = 2, d = NULL) {

}

Oprócz nie ustawiania wartości domyślnej, można też jawnie przypisać wartość NULL.

Wywoływanie funkcji

Argumenty funkcji są wymagane dopiero gdy są potrzebne.

f <- function(a, b) {
        a^2
} 
f(2) #nie ma problemu, bo b jest niepotrzebne
[1] 4

Wywoływanie funkcji

Argumenty funkcji są wymagane dopiero gdy są potrzebne.

f <- function(a, b) {
        print(a) # to się wykona
        print(b) # tu rzuci błędem
}
f(45) # jest problem bo b jest potrzebne

Argument “...”

Argument … oznacza zmienną liczbę argumentów.

  • przydatne przy rozszerzaniu innych funkcji
myplot <- function(x, y, type = "l", ...) {
        plot(x, y, type = type, ...)
}
  • przydatne do tworzenia funkcji generycznych
> mean
function (x, ...)
UseMethod("mean")

Argument “...”

Przydatne również gdy nie znamy liczby parametrów, które zostaną przekazane.

> args(paste)
function (..., sep = " ", collapse = NULL)

> args(cat)
function (..., file = "", sep = " ", fill = FALSE, labels = NULL, append = FALSE)

Argumenty po “...”

Drobna niedogodność polega na tym, że po … argumenty muszą być jawnie nazwane przy wywołaniu i muszą być w pełni dopasowane.

> args(paste)
function (..., sep = " ", collapse = NULL)

> paste("a", "b", sep = ":")
[1] "a:b"

> paste("a", "b", se = ":")
[1] "a b :"

Zasięg widoczności

R tworzy zakresy statyczne. Zakresy nazw są tworzone przez:

  • funkcje (=, <-),
  • przestrzenie nazw (=, <-),
  • przestrzeń globalną (<<-).

Warto zaznaczyć, że w R funkcje mogą być zagnieżdżane oraz że funkcje mogą zwracać funkcje.

Split-Apply-Combine

Pętle są przydatne podczas programowania, ale trudne do wykorzystania podczas interaktywnej pracy ze zbiorem danych. Zamiast pętli można wykorzystać funkcje z rodziny apply:

  • lapply: wykonuje funkcję na elementach listy
  • sapply/vapply: to co lapply tylko z upraszczaniem
  • apply: wywołuje funkcję na wybranych wymiarach
  • tapply: wykonuje funkcję z podziałem na grupy
  • mapply: to co sapply, ale dla wielu list/macierzy

lapply

x <- list(a=1:9, b=rnorm(10), c=rnorm(9, 5))
lapply(x, mean)
$a
[1] 5

$b
[1] 0.3148138

$c
[1] 4.880512

Debugowanie w R

W R można debugować w konsoli.

  • traceback: wypisuje stos wywołań
  • debug: oznacza funkcję do inspekcji
  • browser: wstrzymuje wykonanie funkcji i przełącza w tryb debugowania
  • trace: pozwala wtłoczyć kod debugujący (browser, recover) do funkcji
  • recover: pozwala debugować po wystąpieniu błędu

Debugowanie w RStudio

Debugowanie w RStudio

  1. Source
  2. Source on Save
  3. Punkty wstrzymania

Debugowanie w RStudio

Debugowanie w RStudio

Podsumowanie

  • Filtrowanie danych
  • Instrukcje sterujące
  • Funkcje
  • Split-Apply-Combine
  • Debugowanie

Zadanie domowe

Wykonujemy kolejne lekcje (wszystkie do końca modułu Programowanie w R):

library(swirl.pl)
swirl()