- Los objetos encapsulan información y control de su comportamiento (objects).
- Las clases describen propiedades de un grupo de objetos (class).
- Se pueden definir clases a partir de otras (inheritance).
- Una función genérica se comporta de forma diferente atendiendo a la clase de uno (o varios) de sus argumentos (polymorphism).
En R
coexisten dos implementaciones de la OOP:
S3
: elaboración informal con enfasis en las funciones genéricas y el polimorfismo.S4
: elaboración formal de clases y métodos.
Los objetos básicos en R
tienen una clase implícita definida en S3
. Es accesible con class
.
x <- rnorm(10)
class(x)
Pero no tienen atributo…
attr(x, 'class')
…ni se consideran formalmente objetos
is.object(x)
Se puede redefinir la clase de un objecto S3
con class
class(x) <- 'myNumeric'
class(x)
Ahora sí es un objeto…
is.object(x)
y su atributo está definido
attr(x, 'class')
Sin embargo, su modo de almacenamiento (clase intrínseca) no cambia:
mode(x)
task1 <- list(what='Write an email',
when=as.Date('2013-01-01'),
priority='Low')
class(task1) <- 'Task'
task1
task2 <- list(what='Find and fix bugs',
when=as.Date('2013-03-15'),
priority='High')
class(task2) <- 'Task'
myToDo <- list(task1, task2)
class(myToDo) <- c('ToDo3')
myToDo
notToDo <- list(task1, 2019)
class(notToDo) <- c('ToDo3')
notToDo
Son sencillos de usar e implementar pero poco robustos.
Se definen a partir de un método genérico…
summary
…añadiendo a la función el nombre de la clase con un punto como separador.
summary.data.frame
Con methods
podemos averiguar los métodos que hay definidos para una función particular:
methods('summary')
Si no hay un método definido para la clase del objeto, UseMethod
ejecuta la función por defecto:
summary.default
En primer lugar, definimos la función con UseMethod
:
myFun <- function(x, ...)UseMethod('myFun')
… y la función por defecto.
myFun.default <- function(x, ...){
cat('Funcion genérica\n')
print(x)
}
Dado que aún no hay métodos definidos, esta función ejecutará la función por defecto.
methods('myFun')
x <- rnorm(10)
myFun(x)
myFun(task1)
myFun.Task <- function(x, number,...)
{
if (!missing(number))
cat('Task no.', number,':\n')
cat('What: ', x$what,
'- When:', as.character(x$when),
'- Priority:', x$priority,
'\n')
}
methods(myFun)
methods(class='Task')
myFun(task1)
myFun(task2)
myFun(myToDo)
Define un método de myFun
para la clase ToDo3
con dos enfoques: sin tener en cuenta el método definido para Task
; teniendo en cuenta el método para Task
.
myFun.ToDo3 <- function(x, ...){
cat('This is my ToDo list:\n')
## Cada uno de los elementos de un
## objeto ToDo3 son Task. Por tanto,
## x[[i]] es de clase Task y
## print(x[[i]]) ejecuta el metodo
## print.Task
for (i in seq_along(x)) myFun(x[[i]], i)
cat('--------------------\n')
}
myFun(myToDo)
Se construyen con setClass
, que acepta varios argumentos
Class
: nombre de la clase.slots
: una lista con las clases de cada componente. Los nombres de este vector corresponden a los nombres de los componentes (slot
).contains
: un vector con las clases que esta nueva clase extiende.prototype
: un objeto proporcionando el contenido por defecto para los componentes definidos enslots
.validity
: a función que comprueba la validez de la clase creada con la información suministrada.
Vamos a ilustrar esta sección con datos de seguimiento GPS de gaviotas[fn:1] empleando un extracto del conjunto de datos[fn:2].
setClass('bird',
slots = c(
name = 'character',
lat = 'numeric',
lon = 'numeric',
alt = 'numeric',
speed = 'numeric',
time = 'POSIXct')
)
getClass('bird')
getSlots('bird')
slotNames('bird')
Una vez que la clase ha sido definida con setClass
, se puede crear un objeto nuevo con new
. Es habitual definir funciones que construyen y modifican objetos para evitar el uso directo de new
:
readBird <- function(name, path)
{
csvFile <- file.path(path, paste0(name, ".csv"))
vals <- read.csv(csvFile)
new('bird',
name = name,
lat = vals$latitude,
lon = vals$longitude,
alt = vals$altitude,
speed = vals$speed_2d,
time = as.POSIXct(vals$date_time)
)
}
eric <- readBird("eric", "data")
nico <- readBird("nico", "data")
sanne <- readBird("sanne", "data")
A diferencia de $
en listas y data.frame
, para extraer información de los slots hay que emplear @
(pero no es recomendable):
eric@name
summary(eric@speed)
setClass("flock",
slots = c(
name = "character",
members = "list")
)
notAFlock <- new("flock",
name = "flock0",
members = list(eric,
3,
"hello"))
sapply(notAFlock@members, class)
valida <- function (object) {
if (any(sapply(object@members,
function(x) !is(x, "bird"))))
stop("only bird objects are accepted.")
return(TRUE)
}
setClass("flock",
slots = c(
name = "character",
members = "list"),
validity = valida
)
newFlock <- function(name, ...){
birds <- list(...)
new("flock",
name = name,
members = birds)
}
notAFlock <- newFlock("flock0",
eric, 2, "hello")
myFlock <- newFlock("flock1",
eric, nico, sanne)
- Normalmente se definen con
setMethod
suministrando:- la clase de los objetos para esta definición del
método (
signature
) - la función a ejecutar (
definition
).
- la clase de los objetos para esta definición del
método (
setMethod('show',
signature = "bird",
definition = function(object)
{
cat("Name: ", object@name, "\n")
cat("Latitude: ", summary(object@lat), "\n")
cat("Longitude: ", summary(object@lon), "\n")
cat("Speed: ", summary(object@speed), "\n")
})
eric
setMethod('show',
signature = "flock",
definition = function(object)
{
cat("Flock Name: ", object@name, "\n")
N <- length(object@members)
lapply(seq_len(N), function(i)
{
cat("Bird #", i, "\n")
print(object@members[[i]])
})
})
myFlock
- Es necesario que exista un método genérico ya definido.
isGeneric("as.data.frame")
- Si no existe, se define con
setGeneric
(y quizásstandardGeneric
).
setGeneric("as.data.frame")
- La función
definition
debe respetar los argumentos de la función genérica y en el mismo orden.
getGeneric("as.data.frame")
setMethod("as.data.frame",
signature = "bird",
definition = function(x, ...)
{
data.frame(
name = x@name,
lat = x@lat,
lon = x@lon,
alt = x@alt,
speed = x@speed,
time = x@time)
})
ericDF <- as.data.frame(eric)
Define un método de as.data.frame
para la clase flock
a partir del método para la clase bird
.
setMethod("as.data.frame",
signature = "flock",
definition = function(x, ...)
{
dfs <- lapply(x@members, as.data.frame)
dfs <- do.call(rbind, dfs)
dfs$flock_name <- x@name
dfs
})
flockDF <- as.data.frame(myFlock)
library(lattice)
setGeneric("xyplot")
setMethod('xyplot',
signature = "bird",
definition = function(x, data = NULL, ...)
{
df <- as.data.frame(x)
xyplot(lat ~ lon, data = df, ...)
})
xyplot(eric)
Define un método de xyplot
para la clase bird
que permita elegir entre diferentes modos de representación:
lontime
lattime
latlon
speed
setMethod('xyplot',
signature = "bird",
definition = function(x, data = NULL,
mode = "latlon", ...)
{
df <- as.data.frame(x)
switch(mode,
lontime = xyplot(lon ~ time, data = df, ...),
lattime = xyplot(lat ~ time, data = df, ...),
latlon = xyplot(lat ~ lon, data = df, ...),
speed = xyplot(speed ~ time, data = df, ...)
)
})
xyplot(eric, mode = "lontime")
Define un método de xyplot
para la clase flock
usando el color para distinguir a los diferentes integrantes (argumento group
en xyplot
).
setMethod('xyplot',
signature = "flock",
definition = function(x, data = NULL, ...)
{
df <- as.data.frame(x)
xyplot(lon ~ lat,
group = name,
data = df,
auto.key = list(space = "right"))
})
xyplot(myFlock)
Para usar objetos de clase S3
en signatures
de métodos S4
o
como contenido de slots
de una clase S4
hay que registrarlos con
setOldClass
:
setOldClass('lm')
getClass('lm')
Definimos un método genérico para xyplot
library(lattice)
setGeneric('xyplot')
Definimos un método para la clase lm
usando xyplot
.
setMethod('xyplot',
signature = c(x = 'lm',
data = 'missing'),
definition = function(x, data,
...)
{
fitted <- fitted(x)
residuals <- residuals(x)
xyplot(residuals ~ fitted,...)
})
Recuperamos la regresión que empleamos en el apartado de Estadística:
lmFertEdu <- lm(Fertility ~ Education, data = swiss)
summary(lmFertEdu)
xyplot(lmFertEdu, col='red', pch = 19,
type = c('p', 'g'))
[fn:2]https://lifewatch.inbo.be/blog/files/bird_tracking.zip
[fn:1] https://lifewatch.inbo.be/blog/posts/bird-tracking-data-published.html