R-ohjelmointi.org

Tilastotieteellistä ohjelmointia R-kielellä

R ja putki-operaattori (periaatteessa jo versiosta 1.0.0 alkaen!)

Viime vuosina putkioperaattorin (%>%) käyttö R:ssä on monissa yhteyksissä yleistynyt huomattavasti. Operaattorin tarjoaa varsinaisesti magrittr-paketti, mutta sen yleistymiseen on todennäköisesti syynä sen runsas käyttö tidyversen (tai Hadley Wichamin kehittämän ns. hadleyversen) paketeissa.

Putkioperaattorin käyttö on hyvin yleistä myös esimerkiksi Linuxin komentorivikäytössä. Linuxissa putki on pystyviiva (|), joka lyödään komentojen väliin ja se välittää edellisen komennon tuloksen seuraavalle käsiteltäväksi. Esimerkiksi komennolla ls /etc saadaan etc-hakemiston tiedostot tulostettua ruudulle. Tämä tuloste voidaan putkella ohjata seuraavalle komennolle, vaikkapa rivimäärän laskemista varten tarkoitetulle komennolle wc -l. Putkitettuna siis näin: ls /etc | wc -l. Tai jos tiedostoja on suuri määrä, voidaan joukosta etsiä grep-komennolla jotakin merkkijonoa, vaikka bootloaderin asetustiedostoa (grub.conf): ls /etc | grep -v grub. Saman tapaiseen tarpeeseen on siis tehty R:n putkioperaattori %>%.

Otetaanpa esimerkki R:n putkioperaattorista. Käytetään esimerkkinä iris-datasettiä, joka tulee sisäänrakennettuna R:n mukana. Lasketaan siitä virginica-lajin verholehtien (sepal) leveys ja pituus.

Perus-R:ää (tässä 3.5.0) käyttäen tämän voisi tehdä esimerkiksi seuraavasti:

colMeans(iris[iris$Species=="virginica", c("Sepal.Width", "Sepal.Length")])
 
Sepal.Width Sepal.Length 
      2.974        6.588

Tai pienempiin paloihin eroteltuna näin:

tmp <- iris[iris$Species=="virginica",]
tmp <- tmp[,c("Sepal.Width", "Sepal.Length")]
colMeans(tmp)

tai miksei näinkin:

tmp <- subset(iris, Species=="virginica")
tmp <- subset(tmp, select=c("Sepal.Width", "Sepal.Length"))
colMeans(tmp)

Sama asia voidaan tehdä putkioperaattorilla vaikkapa näin:

library(magrittr)
iris %>%
  subset(Species == "virginica") %>%
  subset(select=c("Sepal.Width", "Sepal.Length")) %>%
  colMeans()

Minusta perus-R:ää käyttävät ilmaisutavat ovat selkeämpiä kuin putken käyttö, mutta tämä voi johtua siitä, että minulla on pitkältä ajalta kertynyt historiallinen painolasti pois opittavaksi. Olen mainaan käyttänyt R:ää versiosta 1.0.0 alkaen. Putkia on puitu pidemmälti esimerkiksi Gavin Simpsonin ja Win-Vector blogeissa. Kannattaa lukea myös kyseisten postausten kommentit.

Painolastista puhuen, tiesittekö, että ”putken” (oikeastaan pisteen) käyttö on ollut mahdollista jo versiosta 1.0.0 alkaen (ks. myös paketti wrapr ja %.>%)? Ei kuitenkaan magrittr:n tarkoittamassa muodossa, vaan näin (versiossa 1.0.0):

data(iris)
iris ->.
  subset(., Species == "virginica") ->.
  subset(., select=c("Sepal.Width", "Sepal.Length")) ->.
  apply(., 2, mean)

Uudempiin R-versioihin verrattuna koodissa on muutama ero. Ensinnäkin data pitää eksplisiittisesti ladata komennolla data(), eikä komentoa colMeans() vielä ole, joten pitää käyttää apply():ä.

Lisäksi putki toimii hieman eri tavalla kuin magrittr:n putki, joka olettaa aina edellisen komennon tuloksen menevän seuraavan komennon ensimmäiseksi argumentiksi. Perus-R:n tarjoamassa versiossa ei näin ole, vaan dataan pitää aina viitata pisteellä (.). Itse asiassa sama pätee magrittr-paketin putkeen, jos data ei ole seuraavan komennon ensimmäinen argumentti. Yksinkertaisella esimerkillä siis vaikkapa näin:

a<-letters
 
# Ei toimi
a %>% gsub("a", "ä")
[1] "ä"
Warning message:
In gsub(., "a", "ä") :
  argument 'pattern' has length > 1 and only the first element will be used
 
# Pisteellä toimii
a %>% gsub("a", "ä", .)
 [1] "ä" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m"
[14] "n" "o" "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z"

Tähänkin on tosin vaihtoehto: paketti regexPipes, joka korvaa muun muassa perus-grepin versiolla, jossa data on ensimmäinen argumentti.


Category