12 Ehtorakenteet
Funktiot kappaleen funktiot suorittavat aina samat komennot riippumatta syötteestä. Entä jos funktion toiminnassa pitäisi ottaa huomioon erilaisia tapauksia, eli suorittaa tiettyjä komentoja vain joissain tilanteissa? Tätä varten ohjelmointikielissä on ehtorakenteita, eli ns. if/else-rakenteita, jotka ohjaavat ohjelman toimintaa.
Tutustutaan ensin tarkemmin loogisiin operaattoreihin.
12.1 Loogiset operaattorit
Tässä on lyhyt lista loogisista operaattoreista:
Operaattori | Toiminto |
---|---|
< | pienempi kuin |
<= | pienempi tai yhtä suuri kuin |
> | suurempi kuin |
>= | suurempi tai yhtä suuri kuin |
== | yhtä kuin |
!= | ei yhtä kuin |
!a | ei a (negaatio) |
a | b | a TAI b alkioittain |
a || b | a TAI b yksittäisille arvoille |
a & b | a JA b alkioittain |
a && b | a JA b yksittäisille arvoille |
a %in% b | mitkä a:n alkiot ovat myös b:n alkioita |
Kaikki loogiset operaattorit palauttavat joko arvon TRUE
, FALSE
tai NA
. Vertailuoperaattorien käyttö on jo tullut tutuksi aikaisemmissa kappaleissa, mutta tutustutaan vähän tarkemmin viimeisten rivien operaattoreihin.
12.1.0.1 Negaatio
Looginen negaatio palauttaa loogisen lauseen vastakohdan, eli muuttaa arvon TRUE
arvoksi FALSE
ja arvon FALSE
arvoksi TRUE
.
10 > 12
## [1] FALSE
!(10 > 12)
## [1] TRUE
# Also works without parentheses
!10 > 12
## [1] TRUE
!is.na(NA)
## [1] FALSE
12.1.0.2 Looginen TAI (disjunktio)
Loogiselle TAI operaattorille annetaan kaksi loogista lausetta, ja TAI operaattori palauttaa TRUE
, jos vähintään toinen lauseista on TRUE
. R:ssä TAI merkitään pystyviivalla |
tai kahdella pystyviivalla ||
. |
käy läpi vektoreita alkioittain, ||
vertaa kahta loogista lausetta, ja toista lausetta ei edes ajeta, jos ensimmäinen on TRUE
(koska ||
palauttaa TRUE
riippumatta toisen lauseen arvosta). Jos tämä tuntui monimutkaiselta, niin riittää muistaa, että ehtorakenteissa kannattaa käyttää muotoa ||
.
10 > 12 || "a" < "b"
## [1] TRUE
2 > 1 || 4 > 2
## [1] TRUE
"a" > "c" || 1 > 10
## [1] FALSE
12.1.0.3 Looginen JA (konjunktio)
Loogiselle JA operaattorille annetaan kaksi lausetta. JA palauttaa TRUE
, jos kummatkin lauseet ovat TRUE
. R:ssä JA-operaattorit ovat &
ja &&
, jotka käyttäytyvät kuten |
ja ||
.
10 > 12 && "a" < "b"
## [1] FALSE
2 > 1 && 4 > 2
## [1] TRUE
"a" > "c" && 1 > 10
## [1] FALSE
12.1.0.4 Osajoukko
%in%
-operaattorilla voi tarkistaa, kuulvatko jotkin arvot johonkin joukkoon. Tämä voitaisiin toteuttaa myös usealla TAI-operaattorilla, mutta %in%
on usein paljon kätevämpi.
<- c("A", "C", "G", "T")
dna_bases <- c("A", "C", "G", "U")
rna_bases
"T" %in% dna_bases
## [1] TRUE
"T" %in% rna_bases
## [1] FALSE
# With negation
!"A" %in% dna_bases
## [1] FALSE
Operaattoria voi soveltaa myös vektoreihin, jolloin operaattori palauttaa loogisen vektorin, jonka alkio jokainen alkio kertoo, kuuluiko vastaava operaation vasemman puolen alkio operaation oikeaan puoleen.
%in% rna_bases dna_bases
## [1] TRUE TRUE TRUE FALSE
12.1.0.5 Monimutkaisemmat lauseet
Operaattoreita voidaan myös yhdistellä monimutkaisemmiksi lauseiksi. Tällöin lauseiden evaluointijärjestys määritetään tarvittaessa suluilla.
<- list(breed = "golden retriever",
dog height = 45,
weight = 27)
$breed == "golden retriever" && dog$weight < 25 || dog$height < 50 dog
## [1] TRUE
12.1.0.6 a < x < b
usein tulee vastaan tilanteita, joissa halutaan tarkistaa, onko jokin luku halutulla välillä. Tämä kirjoitetaan matemaatiisesti esim. näin: \(a < x < b\), jossa tarkastetaan, onko \(x\) välillä \((a, b)\). Tämä ei kuitenkaan valitettavasti toimi R:ssä, vaan tarkistus pitää jakaa kahteen osaan:
# Are x and y between 0 and 1?
<- 3
x <- 0.3
y 0 <= x && x <= 1
## [1] FALSE
0 <= y && y <= 1
## [1] TRUE
12.2 Ehtorakenteet
Aloitetaan esimerkistä: tehtävänä on kirjoittaa funktio, jolle annetaan syötteenä potilaan hemoglobiiniarvo. Funktion on tarkoitus hälyttää, jos hemoglobiini laskee alle viitearvojen alarajan 117. Kyseinen funktio voisi näyttää vaikka tältä:
<- function(hb) {
hb_alert if (hb < 117) {
return("Hemoglobin is low!")
} }
Funktiolla on siis yksi argumentti, hb
eli hemoglobiiniarvo. Funktion sisällä on if
-rakenne. Rakenteessa on kaksi osaa: ehto, ja rakenteen sisäinen koodi. Rakenteen sisäinen koodi ajetaan vain, jos ehto täyttyy. Ehto merkitään if
-komennon jälkeen sulkeisiin, ja rakenteen sisäinen koodi kirjoitetaan sulkeiden jälkeen aaltosulkeiden sisään. (Jos aaltosulkeiden sisään tulisi vain yksi rivi koodia, aaltoasulkeet voi jättää pois, mutta näissä esimerkeissä käytetään aina aaltoasulkeita).
Kokeillaan, miten funktio toimii eri hemoglobiiniarvoilla:
# Nothing happens
hb_alert(130)
# returns alert
hb_alert(110)
## [1] "Hemoglobin is low!"
Funktio siis toimii oletetusti, eli se hälyttää vain, jos hemoglobiinitaso on alle 117. Käyttäjän kannalta olisi kuitenkin kätevää saada jonkinlainen palaute myös silloin, kun hemoglobiinitaso on tarpeeksi korkea. Tätä varten voidaan käyttää else-komentoa:
<- function(hb) {
hb_alert if (hb < 117) {
return("Hemoglobin is low!")
else {
} return("Hemoglobin is OK")
}
}
hb_alert(130)
## [1] "Hemoglobin is OK"
else
-komennon jälkeinen koodi siis ajetaan, jos ehto hb < 117
ei täyty.
Tällä hetkellä funktiomme toimii oikein vain naispotilaille, sillä miehillä hemoglobiiniarvojen alaraja on 134. Lisätään siis funktioomme argumentti sex
sukupuolta varten ja muokataan funktion toimintaa niin, että se osaa ottaa huomioon sukupuolen. Nyt if
-rakenteen ehdosta tulee jo hieman monimutkaisempi:
<- function(hb, sex) {
hb_alert if (sex == "female" && hb < 117 || sex == "male" && hb < 134) {
return("Hemoglobin is low!")
else {
} return("Hemoglobin OK")
}
}
hb_alert(hb = 120, sex = "female")
## [1] "Hemoglobin OK"
hb_alert(hb = 120, sex = "male")
## [1] "Hemoglobin is low!"
Entä jos haluaisimme tulostaa eri varoituksen mies- ja naispotilaille? Tähän tarvitaan else if
-rakennetta:
<- function(hb, sex) {
hb_alert if (sex == "female" && hb < 117) {
return("Hemoglobin is low for a female!")
else if (sex == "male" && hb < 134) {
} return("Hemoglobin is low for a male!")
else {
} return("Hemoglobin OK")
}
}
hb_alert(hb = 110, sex = "female")
## [1] "Hemoglobin is low for a female!"
hb_alert(hb = 120, sex = "male")
## [1] "Hemoglobin is low for a male!"
Nyt funktio tarkistaa ensin, onko potilas nainen ja onko hänen hemoglobiininsa alle 117. Jos ei, siirrytään eteenpäin ja tarkistetaan, onko potilas mies ja onko hänen hemoglobiininsa alle 130. Jos ei, siirrytään viimeiseen kohtaan, ja tulostetaan “Hemoglobin is OK”.
else if
-rakenteita voi olla rajoittamaton määrä ensimmäisen if
-rakenteen jälkeen. Lisätään funktioon hälytys kriittisestä hemoglobiinin määrästä (hb < 50) riippumatta sukupuolesta:
<- function(hb, sex) {
hb_alert if (sex == "female" && hb < 117) {
return("Hemoglobin is low for a female!")
else if (sex == "male" && hb < 134) {
} return("Hemoglobin is low for a male!")
else if (hb < 50) {
} return("Hemoglobin is critical")
else {
} return("Hemoglobin OK")
}
}
hb_alert(hb = 32, sex = "female")
## [1] "Hemoglobin is low for a female!"
Kuten huomataan, yllä oleva koodi ei toimikaan, kuten piti. Näin alhaisella hemoglobiinilla pitäisi tulla varoitus kriittisestä tilasta. Koodi suoritus ei kuitenkaan ikinä etene kriittisen tilan varoitukseen asti, sillä ensimmäinen ehto täyttyy. Korjataan tilanne siirtämällä kriittisen tilan ehto ensimmäiseksi:
<- function(hb, sex) {
hb_alert if (hb < 50) {
return("Hemoglobin is critical")
else if (sex == "male" && hb < 134) {
} return("Hemoglobin is low for a male!")
else if (sex == "female" && hb < 117) {
} return("Hemoglobin is low for a female!")
else {
} return("Hemoglobin OK")
}
}
hb_alert(hb = 32, sex = "female")
## [1] "Hemoglobin is critical"
hb_alert(hb = 120, sex = "female")
## [1] "Hemoglobin OK"
hb_alert(hb = 120, sex = "male")
## [1] "Hemoglobin is low for a male!"
Nyt funktio toimii haluamallamme tavalla!
Funktioissa voi myös olla useampi ehtorakenne. Ehtorakenteita käytetään usein tarkistamaan argumenttien arvoja. Lisätään ehtorakenteet argumenttien tarkistamiseksi:
<- function(hb, sex) {
hb_alert # Hemoglobin should be numeric and positive
if (!is.numeric(hb) || hb < 0) {
return("Hemoglobin should be numeric and positive")
}if (!sex %in% c("female", "male")) {
return("This function can only deal with binary sex: female or male")
}
if (hb < 50) {
return("Hemoglobin is critical")
else if (sex == "male" && hb < 134) {
} return("Hemoglobin is low for a male!")
else if (sex == "female" && hb < 117) {
} return("Hemoglobin is low for a female!")
else {
} return("Hemoglobin OK")
}
}
hb_alert(hb = "120", sex = "female")
## [1] "Hemoglobin should be numeric and positive"
hb_alert(hb = 120, sex = "FEMALE")
## [1] "This function can only deal with binary sex: female or male"
12.3 Alkioiden poimiminen vektorista tietyn ehdon perusteella
Seuraava tilanne on melko tyypillinen: on käytävä läpi vektorin arvot, ja säilytettävä niistä ne, jotka täyttivät tietyn ehdon. Tätä ongelmaa voi lähestyä esimerkiksi seuraavalla tavalla:
- Luo apufunktio, joka ottaa syötteeksi yhden arvon, ja tarkistaa täyttyykö ehto. Tämän funktion tulee palauttaa
TRUE
, jos ehto täyttyy jaFALSE
, jos ehto ei täyty. - Käytä funktiota
Vectorize
, jolla voit muuttaa funktiosi vektoroiduksi funktioksi. Vektorointi tarkoittaa tässä yhteydessä sitä, että yhden alkion sijaan vektoroitua funktiota voidaankin kutsua vektoriargumentilla, ja jokaiselle argumentin alkiolle suoritetaan alkuperäisen funktion määrittelemä operaatio. - Käytä vektoroitua apufunktiota vektorin indeksointiin.
Tässä on esimerkki, jossa käydään läpi vektori DNA:n emäksiä, joista poimitaan vain sytosiinit ja guaniinit.
# Helper function
<- function(base) {
is_cg if (base %in% c("C", "G")) {
return(TRUE)
else {
} return(FALSE)
}
}
# Vectorize
<- Vectorize(is_cg)
is_cg_vector
# Main function
<- function(bases) {
pick_cg <- bases[is_cg_vector(bases)]
only_cg return(only_cg)
}
# NOTE: this only checks the first value of the vector
<- c("A", "C", "C", "T", "G", "T")
my_bases #is_cg(my_bases) # This produces error in 4.2.x
# This works as expected
is_cg_vector(my_bases)
## A C C T G T
## FALSE TRUE TRUE FALSE TRUE FALSE
# Pick only C and G
pick_cg(my_bases)
## [1] "C" "C" "G"