2 Muuttujat ja vektorit

2.1 Muuttujat

Muuttujat (variables) ovat yksi tärkeimmistä ohjelmointikielien rakenteista. Muuttujien tehtävä on säilyttää tietoa ja tuloksia edellisistä laskutoimituksista. Alla on yksinkertainen esimerkki muuttujien käytöstä R:ssä.

x <- 3
y <- 5
z <- x + y

z
## [1] 8

Edellisessä esimerkissä sijoitetaan (assign) eli tallennetaan muuttujaan x arvo 3 ja muuttujaan y arvo 4. Sen jälkeen muuttujien x ja y summa sijoitetaan muuttujaan z, jonka jälkeen tulostetaan muuttujan z arvo. <- on R:n sijoitusoperaattori (myös yhtä kuin-merkki = toimii melkein aina, mutta <- merkin käyttöä suositellaan vahvasti).

Mutta miten muuttujan z arvo tulostui konsoliin, vaikka koodissa ei käytetty funktiota print? R:n erikoisominaisuus moneen muuhun ohjelmointikieleen verrattuna on se, että print-käskyä ei tarvitse aina kirjoittaa, vaan pelkästään muuttujan (tai laskutoimituksen) kirjoittaminen tulostaa arvon konsoliin, kuten alla oleva koodi havainnollistaa:

z
print(z)

x + y
print(x + y)

3 + 5
print(3 + 5)

Muuttujiin voi sijoittaa muutakin kuin yksittäisiä lukuja, kuten merkkijonoja (strings), vektoreita, tai paljon monimutkaisempiakin rakenteita.

x <- "Hello world"
x
## [1] "Hello world"

2.2 Kommentit

Myöhemmin vastaan tulevassa koodissa käytetään kommentteja. Kommentit ovat koodin oheen kirjoitettua tekstiä, joka ei ole ohjelmointikieltä, ja joka ohitetaan koodia ajettaessa. Kommenttien tarkoitus on kuvailla koodin toimintaa. Oman koodin kommentointia on hyvä harjoitella alusta lähtien, vaikka ensimmäisten tehtävien koodi onkin hyvin yksinkertaista. R:ssä kommentit merkataan #-symbolilla. Edellinen esimerkki kommentoituna voisi näyttää jotakuinkin tältä:

# Assign arbitrary numbers to two variables
x <- 3
y <- 5
# Sum of two variables
z <- x + y
# Print the results
z
## [1] 8

2.3 Vektorit

Nyt kun muuttujat ovat tuttuja, voimme siirtyä käsittelemään vektoreita (vector). R:n vektorit ovat yksinkertaisia järjestettyjä tietorakenteita, jotka koostuvat alkioista (elements), esimerkiksi desimaaliluvuista. Alla oleva esimerkki sijoittaa muuttujaan x vektorin, joka sisältää 5 lukua.

x <- c(1, 2, 7.4, 15, 0.2)
x
## [1]  1.0  2.0  7.4 15.0  0.2

Yksinkertaisin tapa tehdä vektori R:ssä on käyttää c-funktiota, joka luo vektorin, jossa on sille annetut arvot annetussa järjestyksessä. Monet R-kielen komennot ja funktiot luovat vektoreita, alla muutama esimerkki:

# Regular sequences
seq(1, 10)
##  [1]  1  2  3  4  5  6  7  8  9 10
seq(0, 1, by = 0.2)
## [1] 0.0 0.2 0.4 0.6 0.8 1.0
seq_len(6)
## [1] 1 2 3 4 5 6
3:9
## [1] 3 4 5 6 7 8 9
# Repeat values
rep(1, 5)
## [1] 1 1 1 1 1
rep(c(1, 2), 3) # Repeat vector c(1, 2) 3 times
## [1] 1 2 1 2 1 2
rep(c(1, 2, 3), 3) # Repeat all values in vector c(1, 2, 3) 3 times
## [1] 1 2 3 1 2 3 1 2 3

2.3.1 Vektorilaskentaa

Vektoreilla laskeminen on usein hyvin intuitiivista (lisää vaaranpaikoista myöhemmin). Kun vektoriin kohdistetaan laskutoimintoja, sama operaatio tehdään kaikille vektorin alkioille (engl. vectorization).

x <- c(1, 2, 3, 6, 10)
x * 2
## [1]  2  4  6 12 20
x / 2 + 1
## [1] 1.5 2.0 2.5 4.0 6.0

Entä jos vektoreita lisää toisiinsa, tai kertoo keskenään? Jos vektorit ovat samanpituisia, operaatio toteutetaan alkio kerrallaan. Jos vektorit ovat eripituisia, R yrittää kierrättää (recycle) lyhyempää vektoria niin, että siitä tulee yhtä pitkä kuin pidempi vektori. Tämän jälkeen operaatio suoritetaan alkio kerrallaan (itse asiassa näin tapahtui myös aiemmissa esimerkeissä, kun vektori kerrottiin yksittäisellä luvulla. R:ssä yksittäiset luvut ovat vektoreita, joiden pituus on 1). Jos kierrätys ei onnistu, eli pidemmän vektorin pituus ei ole jaollinen lyhyemmän pituudella, R antaa virheilmoituksen.

x <- c(1, 2, 3, 6, 10, 2)
y <- c(1, 1, 1, 3, 3, 3) # or rep(c(1, 3), each = 3)
z <- c(2, 4)

x + y # Element-wise sum
## [1]  2  3  4  9 13  5
x * y # Element-wise multipliocation
## [1]  1  2  3 18 30  6
x + z
## [1]  3  6  5 10 12  6

R:ssä on myös paljon funktioita, joilla voi laskea vektoreista erilaisia tunnuslukuja, kuten keskiarvon, mediaanin, keskihajonnan jne.

x <- c(1, 2, 3, 6, 10, 2)
# Sample mean (average)
mean(x)
## [1] 4
# Standard deviation
sd(x)
## [1] 3.405877
# Sum
sum(x)
## [1] 24

2.3.2 Ei-numeeriset vektorit

2.3.2.1 Merkkijonovektorit

Vektorien ei ole pakko sisältää lukuja. Vektorit voivat sisältää esimerkiksi merkkijonoja, kuten alussa nähty “Hello, world!”. Merkkijonotyypin nimi R:ssä on character.

x <- c("Hello, world!", "R is the best", "I", "like", "programming", "!")
x
## [1] "Hello, world!" "R is the best" "I"             "like"         
## [5] "programming"   "!"

Merkkijonovektoreiden muokkausta varten on omia funktiota, tärkeimpinä paste ja paste0, jotka yhdistävät merkkijonoja toisiinsa. Myös numeerisia vektoreita voi antaa näille funktioille, ja ne muutetaan merkkijonoiksi.

first_names <- c("Diana", "Peter", "Bruce")
last_names <- c("Prince", "Parker", "Wayne")
paste(first_names, last_names)
## [1] "Diana Prince" "Peter Parker" "Bruce Wayne"
students <- paste0("Student_", 1:5)

2.3.2.2 Loogiset vektorit

Kolmas yleinen vektorityyppi on looginen vektori, joka sisältää arvoja TRUE eli tosi tai FALSE eli epätosi. Loogisia vektoreita käytetään yleensä joko merkitsemään binäärisiä muuttuja (esimerkiksi paastosiko koehenkilö ennen näytteenottoa) tai vektorien ja matriisien indeksoinnissa (tästä lisää pian). Tällöin loogisia vektoreita syntyy erilaisten loogisten operaattorien avulla:

x <- c(1, 2, 3, 6, 10, 2)

x > 3 # Is the element of x greater than 3?
## [1] FALSE FALSE FALSE  TRUE  TRUE FALSE
x >= 3 # Greater or equal to three=
## [1] FALSE FALSE  TRUE  TRUE  TRUE FALSE
x == 6 # Equal to 6?
## [1] FALSE FALSE FALSE  TRUE FALSE FALSE
x != 2 # Not equal to 2?
## [1]  TRUE FALSE  TRUE  TRUE  TRUE FALSE

2.3.2.3 Loogiset vektorit ja matematiikka

Jos loogiselle vektorille tekee operaation, joka odottaa numeerista vektoria, R muuttaa automaattisesti arvot TRUE ykkösiksi ja arvot FALSE nolliksi. Tämä on erityisen hyödyllistä käytettäessä funktiota sum. Tällä tavalla saadaan helposti tietää esim. kuinka moni vektorin alkio täyttää tietyn ehdon:

x <- c(1, 3, 5, 2, 19)
above_3 <- x > 3

# Logical vector automatically converted to numeric
x + 1
## [1]  2  4  6  3 20
# how many elements of x are smaller than 10?
sum(x < 10)
## [1] 4

2.3.3 Vektorien indeksointi ja osajoukon valinta

Usein vektorista halutaan poimia vain tietyt arvot, esimerkiksi vain ensimmäiset 5 arvoa, tai vain arvot, jotka täyttävät tietyt ehdot. R:ssä vektorin indeksointiin käytetään hakasulkeita []. Yleisin indeksointitapa on antaa hakasulkeiden sisään vektori kokonaislukuja, jotka vastaavat niiden alkioiden järjestyslukuja, jotka vektorista halutaan poimia (HUOM R:ssä indeksointi alkaa ykkösestä, ei nollasta!). Toinen vaihtoehto on käyttää loogista vektoria, jolloin vektorista poimitaan ne alkiot, joiden kohdalla loogisen vektorin arvo on TRUE. Tämä on yksinkertaisempaa kuin miltä se kuulostaa:

x <- c(1, 2, 3, 6, 10, 2)

# Picking exact elements
x[2:3] # Second and third values
## [1] 2 3
x[c(4, 5, 1)] # Note that the order does not have to be increasing
## [1]  6 10  1
# Using logical vector as condition
x[x > 3]
## [1]  6 10
# The condition can be based on another vector
characters <- c("Yoda", "C-3PO", "Rey", "R2-D2", "Anakin", "Baby Yoda")
heights <- c(66, 175, 170, 109, 183, 40.5)
# Only characters shorter than 120 cm
characters[heights < 120]
## [1] "Yoda"      "R2-D2"     "Baby Yoda"

2.3.4 Puuttuvat arvot

Monessa tutkimusprojektissa törmätään syystä tai toisesta jossain vaiheessa puuttuviin arvoihin. Hyvä esimerkki ovat seurantatutkimukset, jossa usein seurannan lopussa on jäljellä vähemmän koehenkilöitä kuin alussa.

Puuttuvia arvoja merkitään R:ssä symbolilla NA (not available). Puuttuvat arvot noudattavat yksinkertaista logiikkaa: mikä tahansa operaatio NA:lle antaa tulokseksi NA. Funktiot, jotka operoivat vektoreilla, kuten sum tai mean voidaan erikseen asettaa poistamaan puuttuvat arvot ennen summan, keskiarvon tms. laskemista.

missing <- c(1, 2, NA, 4, NA, 6)
full <- seq(1, 6)

# Addition with NA returns NA
missing + full
## [1]  2  4 NA  8 NA 12
# Sum of vector with NAs returns NA
sum(missing)
## [1] NA
# Removing NAs before summation
sum(missing, na.rm = TRUE)
## [1] 13

HUOM! Funktio is.na tarkistaa, onko jokin arvo puuttuva. Perinteinen yhtäsuuruuden testaaminen ei siis toimi. Funktio complete.cases muistuttaa is.na funktiota, mutta sitä voidaan käyttää myös kokonaisille aineistoille, jolloin se palauttaa totuusarvon TRUE niiden rivien kohdalla, jotka eivät sisällä lainkaan puuttuvaa tietoa yhdessäkään muuttujassa.

# Just returns NA
NA == NA
## [1] NA
# Returns a logical value as expected
is.na(NA)
## [1] TRUE
is.na(1)
## [1] FALSE
# is.na operates element-wise on a vector
missing <- c(1, 2, NA, 4, NA, 6)
is.na(missing)
## [1] FALSE FALSE  TRUE FALSE  TRUE FALSE
# complete.cases gives the data elements which do not have missing data. 
# It can be used with data frames also.
complete.cases(missing)
## [1]  TRUE  TRUE FALSE  TRUE FALSE  TRUE

2.4 Extra: Alkeistietotyypit ja erikoisarvot

Tässä lisätieto-osiossa käsitellään asioita, joita et välttämättä tarvitse kurssista suoriutuaksesi. Mikäli käytät R:ää enemmän, niin vastaan tulee enemmin tai myöhemmin ongelmia, joissa tarvitsee näitä taitoja. Voit palata näihin myöhemmin koska tahansa, kun haluat syventää ymmärrystäsi R:stä.

R on rakennettu sisäisesti siten, että vektorin kukin elementti on jotain alkeistietotyyppiä. R:ssä on valmiina kuusi alkeistietotyyppiä:

  • looginen (logical)
  • kokonaisluku (integer)
  • numeerinen (eli reaaliluku, numeric)
  • kompleksiluku (complex)
  • merkkijono (character)
  • bitti (raw)

Näistä tarpeellisimpia ovat numeerinen, looginen, ja merkkijono. Kompleksilukua tarvitsee vain joissain erityistapauksissa ja kokonaisluvut ovat nykyään lähes aina tallennettu numeerisina. Bittejä, eli raw-tyypin vektoreita käytetään harvoin.

# Integers are usually stored as reals
x <- c(1, 2, 3)
class(x)
## [1] "numeric"
# You can create integers by adding capital L behind the number
x_int <- c(1L, 2L, 3L)
class(x_int)
## [1] "integer"
# Character strings (or just strings)
x_char <- c("I", "have", "a", "cat.")
class(x_char)
## [1] "character"
# Complex numbers
x_comp <- c(0i, 2 + 1i, 1 - 3i)
class(x_comp)
## [1] "complex"
# Logical vector
x_logi <- c(TRUE, FALSE)
class(x_logi)
## [1] "logical"
# Raw vector
x_raw <- as.raw(c(0, 1, 2))
class(x_raw)
## [1] "raw"

Joskus vektorin tiedot ovat väärässä muodossa, esim. merkkijonoina, mutta niitä haluttaisiin käsitellä numeroarvoina. Näihin operaatioihin on omat funktionsa. Tällöin voi kuitenkin tulla ongelmia, jos muutettava vektori ei ole helposti muutettavissa haluttuun muotoon.

# Chacter string to numeric
class(as.numeric(x_char))
## Warning: NAs introduced by coercion
## [1] "numeric"
# If character contains only values it is easy
x_char2 <- c("0", "5", "6.5")
as.numeric(x_char2)
## [1] 0.0 5.0 6.5
# Integer to numeric
x_int_to_num <- as.numeric(x_int)
x_int_to_num
## [1] 1 2 3
class(x_int_to_num)
## [1] "numeric"
# Numeric to integer
x_num_to_int <- as.integer(x)
x_num_to_int
## [1] 1 2 3
class(x_num_to_int)
## [1] "integer"

Vielä pari lisähuomiota puuttuvista arvoista (näitä ei tarvita usein) koskien niiden tietotyyppejä. Puuttuvalla arvolla on myös alkeistietotyyppi. Mikäli NA-arvon tietotyyppi tulee määritellä, niin sen voi tehdä seuraavasti. Jos luodaan muuttuja tai vektori, jossa on vain yksi arvo, joka on NA, niin se oletusarvoisesti looginen.

# Specify a numeric NA value
NA_real_
## [1] NA
# Specify a complex number NA value
NA_complex_
## [1] NA
# Specify a integer number NA value
NA_integer_
## [1] NA
# Specify a character NA value
NA_character_
## [1] NA
# NA gives a logical type when evaluated alone
class(NA)
## [1] "logical"
# NA_real_ is numeric
class(NA_real_)
## [1] "numeric"

2.4.1 Ääretön ja miinus ääretön

R:ssä on myös ääretön ja miinus ääretön. Ne on toteutettu samaan tapaan kuin puuttuva arvo, mutta niiden tarkasteluun on omat funktionsa. Ääretön ja miinus ääretön arvot syntyvät esimerkiksi silloin, kun nollasta poikkeavia lukuja jaetaan nollalla.

# You can type in infinity or minus infinity if needed
x <- c(1, 2, Inf, 5, -Inf)
# Use is.finite to determine if numbers are finite or not
is.finite(x)
## [1]  TRUE  TRUE FALSE  TRUE FALSE
# Division by zero makes Inf or -Inf (unless 0/0)
x_div_zero <- c(1, 2, -3) / c(3, 0, 0)
x_div_zero
## [1] 0.3333333       Inf      -Inf
is.finite(x_div_zero)
## [1]  TRUE FALSE FALSE

2.4.2 Ei-numero

Mikäli R:ssä sattuu tekemään jonkin matemaattisen toimenpiteen, joka ei ole sallittu, esimerkiksi nollan jaon nollalla tai luvun -1 logaritmin, niin tämä tuottaa R:ssä tietotyypin, joka on NaN (lyhenne sanoista Not a Number). Mikäli NaN-arvoa tutkii funktiolla is.finite tai is.na, niin huomaa että NaN ei ole äärellinen ja NaN tulkitaan NA:ksi.

x_div_zero_by_zero <- 0/0

# Tests for NaN
is.nan(x_div_zero_by_zero)
## [1] TRUE
# Tests if it is finite
is.finite(x_div_zero_by_zero)
## [1] FALSE
# Tests if it is NA (missing)
is.na(x_div_zero_by_zero)
## [1] TRUE