R-ohjelmointi.org

Tilastotieteellistä ohjelmointia R-kielellä

Simppeli sääntökone

Olen jo pitkään tarvinnut useampaankin paikkaan sääntökonetta. Siis sellaista, jolla vaikkapa tutkimusprojektin yhteistyökumppani voi helposti määritellä erilaisia aineiston suodatussääntöjä.

Eräs perinteiden työkalu, jolla sääntöjä määritellään on Java-pohjainen Drools. On olemassa joitakin paketteja, joilla Droolsia voi käyttää R:stä [1, 2, 3], mutta niiden kehitys ja ylläpito näyttää olleen jäissä jo muutamia vuosia. Muita vaihtoehtoja ovat esimerkiksi JESS ja CLIPS, mutta niiden kanssa kommunikoimiseksi ei näytä olevan valmiita R-paketteja. Myös graafisten mallien käyttö asiantuntijajärjestelmien toteuttamiseen voisi tulla kyseeseen (esim. gRain-paketti), mutta ne eivät suoraan sovellu kovin hyvin vaikkapa tarvitsemieni slice and dice -sääntöjen luomiseen.

Niinpä päädyin kokeilemaan, miten vaikeaa sääntöparserin kirjoittaminen oikein olisikaan. Ei kovin vaikeaa, mutta toteutus on vasta kokeiluasteella ja hyvin vajavainen, joskin toimiva. Toteutus mahdollistaa yhden taulukon (data frame) suodatuksen eri muuttujien arvojen perusteella. Suodatuksen tuloksena muodostuvat taulukot palautetaan käyttäjälle (1 per sääntö).

Säännöt määritellään joko tavanomaisia vertailuoperaattoreita käyttäen tai vaihtoehtoisesti luonnollista kieltä käyttäen. Seuraavassa muutamia esimerkkisääntöjä, joita käytetään R:n sisäänrakennetun aineiston (iris) suodattamiseen:

## Sääntö 1
Species on yhtäsuuri kuin "setosa" JA Sepal.Length on suurempi tai yhtäsuuri kuin 5.0

## Sääntö 2
(Species on yhtäsuuri kuin "versicolor" TAI Species on yhtäsuuri kuin "virginica") JA Sepal.Length on suurempi tai yhtäsuuri kuin 6.9

## Sääntö 3
Species kuuluu joukkoon ("versicolor", "virginica") JA Sepal.Length on suurempi tai yhtäsuuri kuin 6.9

## Sääntö 4
(Sepal.Length + Sepal.Width) > 10 JA Species kuuluu joukkoon ("setosa")

## Sääntö 5
(Sepal.Length + Sepal.Width) > 10 JA (EI Species kuuluu joukkoon ("setosa"))  # tämä EI on vähän hankala rakenne

Sääntöjen parsiminen ja käyttäminen vaatii seuraavat R-funktiot, joten nämä pitää ensin ladata R:ään.

 
parse_rules<-function(x) {
   otsikko_rivien_indeksi<-grep("##", x)
   tyhjien_rivien_indeksi<-which(x=="")
   l<-vector("list", length(otsikko_rivien_indeksi))
   names(l)<-gsub("## ", "", x[otsikko_rivien_indeksi])
 
   for(i in 1:length(l)) {
      rule <- x[(otsikko_rivien_indeksi[i]+1):(tyhjien_rivien_indeksi[i]-1)]
 
      rule <- gsub("on yhtäsuuri kuin", "==", rule)
      rule <- gsub("on erisuuri kuin", "!=", rule)
      rule <- gsub("on suurempi tai yhtäsuuri kuin", ">=", rule)
      rule <- gsub("on pienempi tai yhtäsuuri kuin", "<=", rule)
      rule <- gsub("on pienempi kuin", "<", rule)
      rule <- gsub("on suurempi kuin", ">", rule)
 
      rule <- gsub("JA", "&", rule)
      rule <- gsub("TAI", "|", rule)
      rule <- gsub("EI", "!", rule)
 
      rule <- gsub("kuuluu joukkoon \\(", "%in% c\\(", rule)
 
      l[[i]] <- rule
   }
 
   return(l)
}
 
apply_rules<-function(dat, x) {
   result<-vector("list", length(x))
 
   for(i in 1:length(x)) {
      rule<-x[[i]]
 
      tmp<-dat
      tmp<-tmp[with(tmp, eval(parse(text=rule))),]
 
      result[[i]]<-tmp
   }
 
   return(result)
}

Tämän jälkeen sääntöjen käyttäminen onnistuu kahdessa vaiheessa. Ensin muodostetut säännöt ladataan ja parsitaan tekstitiedostosta, ja luonnollisen kielen elementit korvataan R-kielen operaattoreilla. Tämän jälkeen säännöt ajetaan aineistolle. Nämä vaiheet tapahtuvat esimerkiksi seuraavasti:

s<-readLines("säännöt.txt")
x<-parse_rules(s)
res<-apply_rules(iris, x)

Tämän jälkeen suodatuksen jälkeen tuloksena muodostetut aineistot löytyvät objektista res. Aineistoille voidaan lopuksi helposti laskea vaikkapa kuvailevat suureet:

library(psych)
lapply(res, describe)

Yllä kuvattu koodi soveltuu vain varsin yksinkertaisille suodatussäännöille. Käytännössä sääntöjen käyttö aineistolle perustuu R-kielen parse- ja eval -funktioiden käyttöön. Niiden avulla kun voidaan tulkita ja evaluoida (ajaa) R-kielisiä komentoja, kuten juuri tällaisia suodatussääntöjä. Niiden avulla olisi mahdollista toteuttaa paljon monimutkaisempiakin sääntöjä, kunhan ne saadaan käännettyä sopiviksi R-kielisiksi komennoiksi. Funktioiden koodi löytyy myös GitHubista.

Lisätietoja löytyy mm. Hadley Wickhamin kirjasta Advanced R.

Tags: ,


Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *