--- title: Konstrukcje warunkowe author: Bartosz Maćkiewicz subtitle: Statystyka I z R format: html: embed-resources: true code-copy: true lang: pl theme: cosmo format-links: false pdf: pdf-engine: xelatex include-in-header: text: | \usepackage{fvextra} \DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\},breaklines,breakanywhere} \RecustomVerbatimEnvironment{verbatim}{Verbatim}{breaklines,breakanywhere} lang: pl engine: knitr editor: source execute: error: false --- ```{r include=FALSE} if (knitr::is_latex_output()) { knitr::opts_chunk$set(dev = 'cairo_pdf') } ``` # 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 instrukcja `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 instrukcja 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` Funkcja `ifelse` wybiera wartości osobno dla kolejnych elementów wektora. 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. Kolumna `mpg` mówi, ile mil samochód przejedzie na jednym galonie paliwa — im więcej, tym oszczędniej. Przyjmijmy więc: drogie w eksploatacji (do 15 `mpg`), średnie (powyżej 15 do 25 `mpg`) i tanie (powyżej 25 `mpg`). Do takich zadań są specjalne pakiety, ale możemy zrobić to również za pomocą kilku `ifelse`. Dzielimy najpierw samochody na średnie i drogie. ```{r} mtcars['ekonomicznosc'] <- ifelse(mtcars$mpg > 15, 'średni', 'drogi') mtcars$ekonomicznosc ``` Następnie tym, które przejeżdżają więcej niż 25 mil na galonie, przypisujemy 'tani', a resztę wartości zostawiamy. ```{r} mtcars['ekonomicznosc'] = ifelse(mtcars$mpg > 25, 'tani', mtcars$ekonomicznosc) mtcars$ekonomicznosc tail(mtcars) ``` Inny sposób to użycie funkcji `cut`. Za jej pomocą możemy stworzyć `factor` dzieląc zakres wartości na przedziały. Poniżej dzielimy go na trzy przedziały o równej szerokości, więc ich granice będą inne niż użyte przed chwilą 15 i 25. ```{r} cut(mtcars$mpg, 3) cut(mtcars$mpg, 3, labels = c('drogi', 'sredni', 'tani')) ``` ## `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 całkowita jest liczbą pierwszą. Zaimplementujemy kiepski algorytm, który próbuje dzielić ją przez kolejne liczby od 2 do n − 1 i kończy pracę po znalezieniu dzielnika. ```{r} n <- 421 # liczba do sprawdzenia pierwsza <- n >= 2 # liczby mniejsze niż 2 nie są pierwsze d <- 2 # pierwszy potencjalny dzielnik jaki sprawdzamy while(d < n & pierwsza){ if (n %% d == 0){ print(d) pierwsza <- FALSE } d <- d+1 } pierwsza ``` Przerwanie poszukiwania po znalezieniu dzielnika możemy też pokazać za pomocą pętli `for` ze słowem kluczowym `break`. `break` użyte w kodzie sprawia, że R opuszcza pętlę. W naszym wypadku po znalezieniu dzielnika R opuszcza pętlę `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 całkowita n > 1 nie jest pierwsza, to ma dzielnik większy od 1 i nie większy niż pierwiastek z n - 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 <- n >= 2 if (n > 3){ if (n %% 2 == 0 | n %% 3 == 0){ print('Dzielnik to 2 lub 3') pierwsza <- FALSE } else { i <- 5 while (i*i <= n & pierwsza){ if (n %% i == 0 | n %% (i+2) == 0){ print(paste('Dzielnik to', i, 'lub', i+2)) pierwsza <- FALSE } i <- i + 6 } } } pierwsza ```