--- title: "Konstrukcje warunkowe" author: "Bartosz Maćkiewicz" subtitle: Statystyka I z R output: html_document: default pdf_document: default --- # Konstrukcje warunkowe W tym notatniku zajmiemy się różnymi rodzajami konstrukcji warunkowych w R. Podstawowe konstrukcje to: konstrukcjami: - `if / else` - `ifelse` - `for` - `while` Jako materiał dydaktyczny posłuży nam tekst piosenki Ice Cube "It Was A Good Day". W piosence tej Cube opowiada o swoim dobrym dniu. Zacznijmy więc od początku. *Just wakin up in the mornin gotta thank God* *I don't know but today seems kinda odd* *No barkin from the dog, no smog* *[...] I can't believe, today was a good day* Spróbujmy się dowiedzieć, czy dzisiaj był dla Ice Cube'a dobry dzień. Zacznijmy od szczekania psa. ## `if` Najprostszą konstrukcją warunkową jest pętla `if`. Wygląda dokładnie tak samo jak w każdym innym języku programowania - jeżeli warunek jest prawdziwy, to wykonuje okreslone polecenia. ```{r} barkinFromTheDog <- FALSE # wiemy, że pies dzisiaj nie szczekał if (barkinFromTheDog == FALSE) print('It was a good day') ``` Możemy łatwo wprowadzić do naszego wyrażenia negację za pomocą operatora `!`. ```{r} !barkinFromTheDog if (!barkinFromTheDog) print('It was a good day') ``` Oraz konstruować z operatorów funktorów logicznych koniunkcji (`&`) i alternatywy (`|`) bardziej skomplikowane warunki. Nasze instrukcje moga być wielolinikowe, musimy je jednak otoczyć nawiasami klamrowymi (`{}`). ```{r} smog <- FALSE if (barkinFromTheDog == FALSE & smog == FALSE) { print("It was") print('a good day') } ``` Oczywiście jeżeli warunek jest fałszywy, to pętla nie zrobi nic. ```{r} smog = TRUE if (barkinFromTheDog == FALSE & smog == FALSE) { print('It was a good day') } ``` Możemy naturalnie, jak w każdym języku programowania, określić co ma się dziać, jeżeli warunek nie jest spełniony. Służy nam do tego słowo kluczowe `else`. ```{r} if (barkinFromTheDog == FALSE | smog == FALSE) { print('It was a mediocre day') } else { print('It was not a good day') } # To zadziała ``` **Uwaga** - `else` musi być w tej samej linijce, co nawiasy klamrowe okalające blok `if` ```{r eval = F} if (barkinFromTheDog == FALSE | smog == FALSE) { print('It was a mediocre day') } else { print('It was not a good day') } # A to nie zadziała ``` Przy łączeniu kilku warunków w jeden - tak samo jak na logice - należy pamiętać o nawiasach. Te dwie konstrukcje nie są równoważne! ```{r} smog = FALSE if (!(barkinFromTheDog & smog)) { print('It was a good day') } else { print('It was not a good day') } if (!barkinFromTheDog & smog) { print('It was a good day') } else { print('It was not a good day') } ``` Cube rapuje, że w zeszłym tygodniu grając w koszykówkę zdobył *triple-double* czyli dwucyfrową zdobycz w trzech kategoriach statystycznych (punkty, asysty, zbiórki, przechwyty lub bloki) *"Last week [...] around and got a triple double"* Spróbujmy skonstruować odpowiedni warunek. Wykorzystamy do tego interesującą własność R. Stwórzmy najpierw pięć zmiennych, w których będziemy przechowywać liczbę punktów, asyst, zbiórek, bloków i przechwytów. ```{r} points <- 12 assists <- 14 rebounds <- 7 blocks <- 10 steals <- 3 ``` Następnie stwórzmy wektor, w którym znajdą się nasze statystyki ```{r} stats <- c(points, assists, rebounds, blocks, steals) stats ``` Sprawdźmy, ile ze statystyk nich było większe lub równe 10. ```{r} stats >= 10 greather_than_10 <- stats[stats >= 10] greather_than_10 ``` Stwórzmy następnie nową zmienną, która przyjmie wartość `TRUE` tylko wtedy, gdy Ice Cube zdobył *triple-double*. ```{r} triple_double <- length(greather_than_10) >= 3 triple_double ``` Następnie możemy włączyć naszą nową zmienną do warunku w konstrukcji warunkowej `if`. ```{r} if (!(barkinFromTheDog & smog) & triple_double) { print('It was a good day') } else { print('It was not a good day') } ``` ## `ifelse` Konstrukcja `ifelse` nie jest prawdziwą pętlą, ale jest do pętli podobna i można jej użyć do bardzo wielu zadań. Funkcja ta przyjmuje trzy argumenty i zwraca wektor. Ogólna konstrukcja wygląda tak: ```{r eval = F} ifelse(test,yes,no) ``` lub bardziej opisowo ```{r eval = F} ifelse(warunek, wartosc_kiedy_spelniony, wartosc_kiedy_niespelniony) ``` - `warunek` - wektor typu `logical` - `wartosc_kiedy_spelniony` - wartość w wynikowym wektorze dla pozycji, dla których w `warunek` znajduje się `TRUE` - `wartosc_kiedy_niespelniony` - wartość w wynikowym wektorze dla pozycji, dla których w `warunek` znajduje się `FALSE` Jest ona o tyle przydatna, że jeżeli przekażemy jej na wejściu wektor, to warunek będzie sprawdzany dla każdego elementu i funkcja również zwróci wektor. Przećwiczmy wykorzystanie tej funkcji na przykładzie znanego nam zbioru danych `mtcars`. ```{r} data(mtcars) # załadujmy zbiór mtcars head(mtcars) # pierwsze 6 wierszy ``` Dzięki funkcji `ifelse` łatwo możemy pogrupować samochody na szybkie i wolne. Przyjmijmy arbitralną granicę, że 150 koni mechanicznych oznacza, że samochód jest szybki. ```{r} head(mtcars$hp) ifelse(mtcars$hp > 150, 'szybki', 'wolny') ``` Możemy teraz do naszej ramki danych dołączyć dodatkową kolumnę z informacją, czy dany samochóð jest szybki. ```{r} mtcars['predkosc'] = ifelse(mtcars$hp > 150, 'szybki', 'wolny') ``` ```{r} head(mtcars, 7) ``` A co jeżeli mamy więcej niż dwie kategorie? Załóżmy, że chcemy podzielić samochody pod względem ekonomiczności na 3 kategorie - tanie (do 15 w kolumnie `mpg`), średnie (między 16 a 25 w kolumnie `mprg`) i drogie (więcej niż 25 w kolumnie `mpg`) ze względu na to ile palą benzyny. Do takich zadań są specjalnie pakiety, ale mozemy zrobić to również za pomocą kilku `ifelse`. Dzielimy najpierw samochody na średnie i tanie. ```{r} mtcars['ekonomicznosc'] <- ifelse(mtcars$mpg > 15, 'średni', 'tani') mtcars$ekonomicznosc ``` Następnie tym, które przekraczają próg "drogości" przypisujemy 'drogi', a resztę wartości zostawiamy. ```{r} mtcars['ekonomicznosc'] = ifelse(mtcars$mpg > 25, 'drogi', mtcars$ekonomicznosc) mtcars$ekonomicznosc tail(mtcars) ``` Inny sposób to użycie funkcji `cut`. Za za jej pomocą możemy stworzyć `factor` dzieląć wektor liczbowy na równe części. ```{r} cut(mtcars$mpg, 3) cut(mtcars$mpg, 3, labels = c('tani', 'sredni', 'drogi')) ``` ## `for` Pętla `for` wygląda dość standardowo. Jej konstrukcja wygląda następująco: ```{r eval = F} for (wartosc in wektor) polecenie` ``` Wewnątrz pętli `for` możemy odwoływać się do aktualnej wartości za pomocą jej nazwy (`wartosc`). Wyświetlenie wszystkich liczb od 1 do 10 i od 20 do 30 możemy więc wykonać w ten sposób: ```{r} for (i in 1:10) print(i) ``` ```{r} for (i in 20:30) print(i) ``` Staramy się nie robić tak, jak w przykładzie niżej. Nie dlatego, że to nie działa, ale dlatego, że uznawane jest to za kiepski styl w R. Jeżeli mamy wektor, po którym możemy iterować, to nie powinniśmy wprowadzać dodatkowych zmiennych w naszym kodzie. W wielu językach programowania konstrukcje, takie jak ta poniżej, są w porzadku, w R jednak nie powinniśmy ich używać. ```{r} n <- 20 for (i in 1:11){ print(n) n <- n + 1 } ``` Wektory po których iteruje pętla `for` nie muszą być liczbami. w R możliwe jest iterowanie po dowolnym wektorze. ```{r} rymowanka <- c('Entliczek', 'Pętliczek', 'Czerwony', 'Stoliczek') for (slowo in rymowanka){ # tak robimy w R print(slowo) # możemy odwołać się do wartości za pomocą `slowo` } ``` Dlatego też staramy się nie robić tak jak tutaj: ```{r} for(i in 1:length(rymowanka)) print(rymowanka[i]) # tak nie robimy w R PROSZĘ! ``` Czasami będziemy jednak do tego zmuszeni. Wyobraźmy sobie, że chcemy za pomocą R stworzyć krótkie podsumowanie naszej dotychczasowej pracy nad samochodami. Nauczymy się później jak zrobić to bardziej idiomatycznie dla R, teraz zróbmy to jednak za pomocą pętli `for`. Idea jest taka, żeby dla każdego samochodu wydrukować na ekranie zdanie dotyczace jego ekonomiczności. Skorzystamy w tym celu z funkcji `paste`. ```{r} for (wiersz in 1:nrow(mtcars)){ zdanie <- paste( # funkcja umożliwiająca konkatenację zmiennych typu `character` row.names(mtcars)[wiersz], # row.names służy do wydobywania z ramki danych nazw wierszy 'to samochód', mtcars[wiersz, 'ekonomicznosc'], # wybieramy dla każdego samochodu wartość z kolumny `ekonomicznosc` 'w eksploatacji.' ) print(zdanie) } ``` Jeżeli już *koniecznie* musimy z jakiegoś powodu iterować po każdej wartości z macierzy, możemy to zrobić za pomocą zagnieżdżonych dwóch pętli `for`. Spróbujmy stworzyć macierz i dodać 5 do każdej niepodzielnej przez trzy liczby oraz odjąć 5 od każdej podzielnej przez 3. ```{r} macierz = matrix(1:25, 5,5) macierz ``` ```{r} # Podzielność przez 3 sprawdzamy operatorem modulo czyli % for (kolumna in 1:ncol(macierz)){ # pierwsza pętla for iteruje po kolumnach for (wiersz in 1:nrow(macierz)){ # druga pętla for po wierszach if (macierz[wiersz,kolumna] %% 3 == 0){ macierz[wiersz,kolumna] <- macierz[wiersz,kolumna] - 5} else { macierz[wiersz,kolumna] <- macierz[wiersz,kolumna] + 5} }} macierz ``` Bardziej obeznany z R zrobiłby to oczywiście inaczej. Niezłym pomysłem jest przekonwertowanie macierzy do postaci wektora, zaaplikowanie funkcji `ifelse` i stworzenie z wektora nowej macierzy. ```{r} macierz <- matrix(1:25, 5,5) m <- as.numeric(macierz) matrix(ifelse(m %% 3 == 0, # warunek m - 5, # jeśli warunek spełniony m + 5) # jeśli warunek niespełniony ,5,5) ``` # `while` Istotą pętli `while` jest powtarzanie pewnego bloku kodu, dopóki `warunek` jest prawdziwy. ```{r eval = F} while (warunek) polecenie ``` Spróbujmy za pomocą pętli `while` stworzyć kod do sprawdzania, czy dana liczba jest liczbą pierwszą implementując kiepski algorytm, który próbuje podzielić ją przez kolejne liczby naturalne większe od 2. ```{r} n <- 421 # liczba do sprawdzenia pierwsza <- FALSE # czy jest to liczba pierwsza? d <- 2 # pierwszy potencjalny dzielnik jaki sprawdzamy while(!pierwsza){ if (n %% d == 0){ print(d) pierwsza = TRUE } d <- d+1 } pierwsza ``` Zamiast while możemy użyć pętli `for` ze kluczowym słowem `break`. `break` użyte w kodzie sprawia, że R "ucieka" z nadrzędnej pętli. W naszym wypadku po znalezieniu dzielnika R opuszcza nadrzędną pętle `for`. ```{r} n <- 35 for(i in 2:n){ if(n %% i == 0){ print(paste('Dzielnik to', as.character(i))) break # jeżeli warunek się spełni, to nie wykona się już żadna iteracja `for` } else{ print(paste(as.character(i), 'nie jest dzielnikiem.')) } } ``` Tutaj mamy trochę lepszy algorytm, który wykorzystuje fakt że: - jeżeli liczba $n$ nie jest liczbą pierwszą, to pierwiastek każdego jej dzielnika jest od niej mniejszy - po sprawdzeniu podzielności przez $2$ nie musimy sprawdziać, czy dana liczb dzieli się przez liczby parzyste - po sprawdzeniu podzielności przez $3$ nie musimy sprawdzać, czy dzieli się przez wielokrotności $3$ ```{r} n <- 421 pierwsza <- FALSE while(!pierwsza){ if (n <= 3){ print('To jest liczba pierwsza') pierwsza = TRUE } if (n %% 2 == 0 | n %% 3 == 0){ print('Dzielnik to 2 lub 3') pierwsza = FALSE } i = 5 while (sqrt(i) < n){ if((n %% i) == 0 | (n %% (i+2)) == 0){ p <- paste('Dzielnik to', as.character(i), 'lub', as.character(i+2)) print(p) pierwsza = TRUE} i <- i + 6 } } ```