Instrukcja warunkowa if

W tym rozdziale zapoznamy się z tworem zwanym instrukcją warunkową. Dzięki niemu możemy kazać programowi coś zrobić tylko pod pewnym warunkiem. Instrukcję warunkową tworzy się za pomocą słowa "if", czyli po prostu "jeśli".

Wygląda to następująco:

> if ( prawdopodobienstwo_wypadku_samochodem < prawdopodobienstwo_wypadku_samolotem ) {
+   print("Jedzmy samochodem, tak bedzie bezpieczniej")
+ }

(Załóżmy, że gdzieś wcześniej w programie zdefiniowaliśmy już zmienne prawdopodobienstwo_wypadku_samochodem i prawdopodobienstwo_wypadku_samolotem. Pamiętaj, że żeby przetestować, czy ten if zadziała, te zmienne muszą istnieć.)

Całą powyższą instrukcję możemy przeczytać następująco: jeśli zmienna prawdopodobienstwo_wypadku_samochodem jest mniejsza od zmiennej prawdopodobienstwo_wypadku_samolotem, napisz "Jedzmy samochodem, tak bedzie bezpieczniej". R widząc instrukcję if oblicza wyrażenie w nawiasach - w tym wypadku, porównuje wartości dwóch zmiennych - i jeżeli wyszło mu TRUE, wykonuje wszystkie polecenia wewnątrz nawiasów klamrowych; jeśli wyszło mu FALSE, idzie dalej ignorując wszystkie polecenia, aż nie dojdzie do nawiasu klamrowego zamykającego tego if-a.

Zauważ, że kiedy napisaliśmy if (...) { i nacisnęliśmy enter, kolejna linia zaczęła się od plusa - R wie, że to jeszcze nie koniec, że napisana przez nas linia sama nie sensu, że jest wstępem do całego bloku instrukcji, i będzie uparcie zaczynał kolejne linie od plusa tak długo, jak długo nie skończymy bloku instrukcji poprzez zamknięcie nawiasu:instrukcją warunkową

> if (TRUE) {
+
+
+
+
+
+
+ # alez uparty z tymi plusami
+
+
+ }
NULL

W tym przypadku warunek podany if-owi był na pewno spełniony - w końcu TRUE zawsze jest warte TRUE, ale wewnątrz ifa nie było żadnego polecenia, tylko jeden komentarz, więc wynikiem działania tego ifa okazał się NULL - nic.

W pierwszym przykładzie polecenie było tylko jedno, i tak naprawdę moglibyśmy sobie darować te nawiasy klamrowe, tak też by zadziałało:

> if ( prawdopodobienstwo_wypadku_samochodem < prawdopodobienstwo_wypadku_samolotem )
+   print("Jedzmy samochodem, tak bedzie bezpieczniej")

ale kiedy poleceń jest więcej, R sam się przecież nie domyśli, które należą jeszcze do ifa, a które są po ifie i ma wykonać niezależnie od podanego ifowi warunku; musimy mu o tym powiedzieć otaczając nawiasami klamrowymi polecenia, które mają należeć do ifa. Spójrzmy na poniższy przykład, gdzie ifowi podajemy kolejny idiotyczny warunek, tym razem zawsze fałszywy:

> if(FALSE)
+     print("wcale nie chce zeby to sie wykonalo")
>     print("to tez ma sie nie wykonac")
[1] "to tez ma sie nie wykonac"
>  # niech to, wykonalo sie
> if(FALSE) {
+      print("wcale nie chce zeby to sie wykonalo")
+      print("to tez ma sie nie wykonac")
+ }
>  # nic sie nie wykonalo, yay

Może rzuciło Ci się w oczy, że stawiam spacje przed poleceniami należącymi do ifa. Nie jest to konieczne, ale ułatwia to zdecydowanie czytanie programu; zwłaszcza, kiedy więcej ifów i innych konstrukcji zagnieździ się w sobie. Dzięki robieniu wcięć wiadomo, że wszystkie polecenia, które znajdują się w tym samym pionie należą do tego samego bloku. Wiele edytorów tekstowych (np. RStudio) automatycznie robią wcięcia po linijkach z ifem czy innymi instrukcjami, po których wcięcia by się przydały.

Oprócz "if" pożytecznym słowem kluczem jest "else", czyli "w przeciwnym wypadku":

> if (ryzyko_powodzi > ryzyko_wybuchu_wulkanu) {
+     print("zamieszkajmy w islandii")
+ } else {
+     print("zamieszkajmy nad morzem")
+ }

R sprawdzi wartość wyrażenia w nawiasie, tj. sprawdzi, czy ryzyko_powodzi jest większe niż ryzyko_wybuchu_wulkanu; jeśli wyjdzie mu TRUE, wykona to, co jest w ifie, czyli wydrukuje "zamieszkajmy w islandii", a w przeciwnym wypadku (jeśli wyjdzie mu FALSE): wykona to, co jest w bloku komend po "else", czyli wydrukuje "zamieszkajmy nad morzem".

Ważna uwaga. Dopóki R wie, że nie skończyliśmy pisać instrukcji, możemy wciskać sobie enter ile razy nam się żywnie podoba; ale jeżeli zamkniemy nawias klamrowy po ifie i naciśniemy enter, R pomyśli, że skończyliśmy już, sprawdzi warunek w ifie i - zależnie od tego czy wyszedł mu prawdziwy czy fałszywy - wykona polecenia w nawiasach klamrowych lub nie. R nie ma pojęcia, czy chemy potem dopisać jakiegoś else'a. Dlatego jeśli piszemy else'a, musimy to zrobić w tej samej linii, w której znajduje się zamykający nawias klamrowy. Za tym jednym, dosyć zresztą logicznym, wyjątkiem entery w ifie możemy pacać gdzie nam się podoba:

> if
+ (
+ a
+ >
+ b
+
+ )
+ {
+
+ a = b *
+ 2
+ b = a - 19
+
+ } else
+
+
+ {
+
+ print("kiedy
+
+ to sie skoczy?...
+ "
+ )
+ }

No i tu pora przyznać, że właściwie pisząc "entery możemy pacać gdzie nam się podoba" mam w szczególności na myśli, że nie musimy ich pacać wcale. I to nie tylko w ifie, w ogóle wszędzie. Oczywiście R musi wiedzieć, że skończyliśmy jedno polecenie i zaczynamy następne, ale zamiast oddzielać enterem możemy je oddzielić średnikiem. Zdecydowanie tego nie polecam, bo wygląda zupełnie nieczytelnie, zresztą podobnie jak wciskanie enterów rozdzielających polecenia, jak wyżej. Zresztą, oceńcie sami:

> a = 4; b = 10; if( a > b ) { a = b * 2; b = a - 19 } else { print("borze co za tasiemiec") }

No nie najlepiej to wygląda...

Ciekawym poleceniem jest ifelse(), który działa następująco: jako warunek dostaje wektor wartości TRUE / FALSE, a jako wynik zwraca wektor tej samej długości, z elementami wyliczonymi za pomocą jednego z dwóch podanych poleceń, pierwszego - gdy na danym miejscu było TRUE, lub drugiego w przeciwnym wypadku. Na przykład:

> ifelse( c(TRUE, FALSE, FALSE), "prawda", "falsz")
[1] "prawda" "falsz"  "falsz" 

Trochę bardziej wyrafinowany przykład:

> ( wektor = 1:3 )
[1] 1 2 3
> ifelse( c(TRUE, TRUE, FALSE), wektor + 2, wektor - 1 )
[1] 3 4 2

Na dwóch pierwszych miejscach mamy elementy wektora nazwanego wektor powiększone o dwa, na ostatnim - ostatni element zmniejszony o jeden. I jeszcze bardziej wyrafinowany przykład, bo tym razem warunku nie dostajemy na tacy, a obliczamy:

> ( wektor = 1:9 )
[1] 1 2 3 4 5 6 7 8 9
> ifelse( wektor %% 2 == 1, 0, wektor / 2 )
[1] 0 1 0 2 0 3 0 4 0

Warunkiem tu było wyrażenie "wektor %% 2 == 1", które zwróciło wektor wartości FALSE lub TRUE (zależnie od tego, czy liczba była parzysta, czy nie; przypominam, że %% oznacza resztę z dzielenia, a więc element wektora daje resztę z dzielenia przez 2 równą 1 gdy jest nieparzysty). Następnie zwraca wektor tej samej długości, który dla pozycji, gdzie były liczby nieparzyste ma zero, a dla pozostałych: tą liczbę podzieloną przez dwa.

Wyposażony w tę wiedzę możesz już spróbować zmierzyć się z zadaniem Operacje na plikach lub Instrukcje warunkowe.