R Orientado a Objetos e um exemplo S4

A gente já falou de programação orientado a objetos aqui, usando como exemplo classes do tipo S3.

Mas existe outra forma de definir classe no R que é a S4. Essa é uma forma que foi desenvolvida posteriormente ao método S3, e tem mais uma cara de orientação a objetos, no sentido que ela é mais estrita nas definições dela.

Bem vamos começar redefinindo nossa classe armadilha de sementes.

01

Então, primeiro definimos nossas classes, setando quais os seus atributos

1
2
3
4
#definindo classe
setClass("armadilhaDeSementes",representation(distancias = "numeric",
                                       contagem.sementes = "numeric",
                                       area.armadilha = "numeric"))

Depois nos podemos fazer um construtor, uma função que vai construir objetos dessa classe. Lembrando que essa parte é interessante e importante, pois é a hora que podemos validar os objetos, para que os atributos não tenham valores que não façam sentido.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#construtor
setMethod("initialize","armadilhaDeSementes",
          function(.Object,
                   distancias = numeric(0),
                   contagem.sementes = numeric(0),
                   area.armadilha = numeric(0)) {
    if (length(distancias) != length(contagem.sementes)) {
        stop("Quantidade de distâncias e contagens é diferente.")
    }
    if (length(area.armadilha) != 1) {
        stop("Área da armadilha é ambigua.")
    }
    .Object@distancias <- distancias
    .Object@contagem.sementes <- contagem.sementes
    .Object@area.armadilha <- area.armadilha
    .Object
})

Agora a primeira diferença entre as classes s3 e s4 visível, é que objetos da classes s4 so podem ser construídos pela função new.

> s1 <- new("armadilhaDeSementes",distancias = 1:4, + contagem.sementes = c(4, 3, 2, 0), + area.armadilha = 0.0001) >

Mas veja que para ficar com uma cara do que vemos no R, basta fazer uma função que chama a função new.

1
2
3
4
#criando uma função para criar objetos s4
armadilhaDeSementes <- function(...) {
    new("armadilhaDeSementes",...)
}

Agora a gente pode criar objetos da forma convencional, que sempre vemos.

> s2<-armadilhaDeSementes(distancias=1:4,contagem.sementes=c(4, 3, 2, 0), + area.armadilha=0.0001) > class(s2) [1] "armadilhaDeSementes" attr(,"package") [1] ".GlobalEnv" >

Veja que esse é um bom exemplo do uso do …, quando uma função tem … como argumento, o que ela faz é pegar tudo que está como argumento, depois dos definidos nela, e passa pra frente, aqui a função armadilhaDeSementes pega tudo que tem de argumento dela e manda direto para o new(“armadilhaDeSementes”,…)

Outra coisa, é que para ver quais atributos uma classe tem, a gente usa a função slotNames

> slotNames(s1) [1] "distancias" "contagem.sementes" "area.armadilha"

E acessa os valores usando o arroba, ao invés do $

> #acessando os atributos > s1@distancias [1] 1 2 3 4

A gente vai definir métodos usando setMethod, colocando como assinatura qual a classe de que ela se trata.

1
2
3
4
5
6
7
8
9
10
11
12
#definindo métodos
setMethod("show",signature(object = "armadilhaDeSementes"),
          function(object) {
              str(object)
          }
          )
#
setMethod("mean",signature(x = "armadilhaDeSementes"),
          function(x, ...) {
              weighted.mean(x@distancias,w = x@contagem.sementes)
          }
          )

A essa funções vão ser atribuídas a essa classe direto, sem ter usar o esquema do ponto como nas classes S3. Aqui também existe mais uma nuance. Toda função deveria retornar algo, mesmo que retorne nada, um null, mas as funções no R, quanto nada é definido para um return, ela retorna a “coisa” da ultima linha da função, por isso o str(object) e weighted.mean(x@distancias,w = x@contagem.sementes) não estão dentro de um return(), mas se estivessem, teríamos a mesma coisa no fim das contas.

> s1 Formal class 'armadilhaDeSementes' [package ".GlobalEnv"] with 3 slots ..@ distancias : int [1:4] 1 2 3 4 ..@ contagem.sementes: num [1:4] 4 3 2 0 ..@ area.armadilha : num 1e-04 > mean(s1) [1] 1.777778

Cuidado, pois dessa forma, methods mostra apenas funções S3 (espero que não esteja falando besteira aqui).

> methods(mean) [1] mean.Date mean.default mean.difftime mean.POSIXct mean.POSIXlt

Precisamos usar o showMethods para ver os métodos S4

> showMethods("mean") Function: mean (package base) x="ANY" x="armadilhaDeSementes"

O showMethods também pode mostrar os métodos associados a uma classe, com uma determinada assinatura.

> showMethods(classes = "armadilhaDeSementes") Function: initialize (package methods) .Object="armadilhaDeSementes" Function: mean (package base) x="armadilhaDeSementes" Function: show (package methods) object="armadilhaDeSementes"

E a função getMethod mostra o código fonte da função, para classes S3 basta a gente digitar o nome da função sem parenteses, mas para funções S4 a gente usa o getMethod.

> getMethod("mean", "armadilhaDeSementes") Method Definition: function (x, ...) { weighted.mean(x@distancias, w = x@contagem.sementes) } Signatures: x target "armadilhaDeSementes" defined "armadilhaDeSementes"

Bem é isso ai, um tutorial legal para ver mais coisas de classes S4, é o tutorial do Hadley Wickham aqui (alias esse cara tem excelente tutoriais, vale a pena navegar na página dele aqui, e hey, ele fez o ggplot2), o script vai estar la no repositório recologia, e se eu escrevi alguma bobeira, algo errado, deixe um comentário corrigindo, ou mande um e-mail e até mais.

Referência:
Owen Jones, Robert Maillardet & Andrew Robinson 2009 – Introduction to Scientific Programming and Simulation Using R. CRC Press 499pp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#definindo classe
setClass("armadilhaDeSementes",representation(distancias = "numeric",
                                       contagem.sementes = "numeric",
                                       area.armadilha = "numeric"))
 
#construtor
setMethod("initialize","armadilhaDeSementes",
          function(.Object,
                   distancias = numeric(0),
                   contagem.sementes = numeric(0),
                   area.armadilha = numeric(0)) {
    if (length(distancias) != length(contagem.sementes)) {
        stop("Quantidade de distâncias e contagens é diferente.")
    }
    if (length(area.armadilha) != 1) {
        stop("Área da armadilha é ambigua.")
    }
    .Object@distancias <- distancias
    .Object@contagem.sementes <- contagem.sementes
    .Object@area.armadilha <- area.armadilha
    .Object
})
 
#instanciando um objeto
s1 <- new("armadilhaDeSementes",distancias = 1:4,
          contagem.sementes = c(4, 3, 2, 0),
          area.armadilha = 0.0001)
 
#criando uma função para criar objetos s4
armadilhaDeSementes <- function(...) {
    new("armadilhaDeSementes",...)
}
 
#instanciando um objeto
s2<-armadilhaDeSementes(distancias=1:4,contagem.sementes=c(4, 3, 2, 0),
                 area.armadilha=0.0001)
class(s2)
 
#atributos da classe
slotNames(s1)
 
#acessando os atributos
s1@distancias
 
 
#definindo métodos
setMethod("show",signature(object = "armadilhaDeSementes"),
          function(object) {
              str(object)
          }
          )
#
setMethod("mean",signature(x = "armadilhaDeSementes"),
          function(x, ...) {
              weighted.mean(x@distancias,w = x@contagem.sementes)
          }
          )
 
#usando método show
s1
 
#método generico
mean(s1)
 
#métodos s3 de mean
methods(mean)
 
#métodos com assinatura s4
showMethods("mean")
 
#métodos da classe
showMethods(classes = "armadilhaDeSementes")
 
#vendo o código do método mean da classe armadilhaDeSementes
getMethod("mean", "armadilhaDeSementes")

Leave a Reply

Your email address will not be published. Required fields are marked *