Подтвердить что ты не робот

Классы в R с фона python

Я программист на python, и это мой первый день работы с R.

Я пытаюсь написать класс с конструктором и тремя методами, и я боюсь.

В python это легко:

 class MyClass:
      def __init__(self):
           self.variableA = 1
           self.variableB = 2

      def hello(self):
           return "Hello"

      def goodbye(self):
           return "Goodbye"

      def ohDear(self):
           return "Have no clue"

Я не могу найти ничего, что показало бы мне, как сделать что-то столь же простое, как это в Р. Я был бы признателен, если бы кто-нибудь мог показать мне один способ сделать это?

Большое спасибо

4b9b3361

Ответ 1

R фактически имеет множество различных объектно-ориентированных реализаций. Три родных (как упоминается @jthezzel) являются S3, S4 и ссылочными классами.

S3 - это легкая система, которая позволяет вам перегружать функцию, основанную на классе первого аргумента.

Справочные классы предназначены для более близкого соответствия классам с другими языками программирования. Они более или менее заменяют классы S4, которые делают то же самое, но более громоздко.

Пакет R.oo предоставляет другую систему, а пакет proto позволяет использовать прототип программирования, который похож на легкий ООП. В пакете OOP была шестую систему, но теперь она не функционирует. Более поздний R6 пакет "- это более простая, быстрая, более легкая альтернатива R-образным ссылочным классам" .

Для новых проектов вы обычно хотите использовать классы S3 и Reference (или, возможно, R6).

классы python легче всего перевести на эталонные классы. Они относительно новы и (пока Джон Чамберс не закончит свою книгу) лучшая ссылка - это ?ReferenceClasses. Вот пример, чтобы вы начали.

Чтобы определить класс, вы вызываете setRefClass. Первый аргумент - это имя класса, и по соглашению это должно быть таким же, как переменная, которой вы назначаете результат. Вам также необходимо передать list в аргументы "поля" и "методы".

Есть несколько причуд.

  • Если вы не хотите указывать, какой тип переменной должно иметь поле, передайте "ANY" в качестве значения в списке полей.
  • Любая логика конструктора должна быть в необязательной функции initialize.
  • Если первая строка метода является строкой, она интерпретируется как документация для этого метода.
  • Внутри метода, если вы хотите назначить поле, используйте глобальное присвоение (<<-).

Это создает генератор класса:

MyClass <- setRefClass(
  "MyClass",
  fields = list(
    x = "ANY",
    y = "numeric",
    z = "character"
  ),
  methods = list(
    initialize = function(x = NULL, y = 1:10, z = letters)
    {
      "This method is called when you create an instance of the class."
      x <<- x
      y <<- y
      z <<- z
      print("You initialized MyClass!")
    },
    hello = function()
    {
      "This method returns the string 'hello'."
      "hello"
    },
    doubleY = function()
    {
      2 * y
    },
    printInput = function(input)
    {
      if(missing(input)) stop("You must provide some input.")
      print(input)
    }
  )
)

Затем вы создаете экземпляры класса, вызывая объект-генератор.

obj1 <- MyClass$new()
obj1$hello()
obj1$doubleY()

obj2 <- MyClass$new(x = TRUE, z = "ZZZ")
obj2$printInput("I'm printing a line!")

Дальнейшее чтение: полевое руководство OO в разделе Advanced R.

Ответ 2

Недавно я написал сравнение классов python и классов R S4, которые можно найти по адресу:

http://practicalcomputing.org/node/80

Классы очень разные в R и python, как в том, как они объявлены, как они используются, так и в том, как они работают.

В запросе mbinette в комментариях, вот полный текст сообщения (минус большинство гиперссылок, так как у меня есть только привилегии для двоих):

Для тех, кто запрограммировал в python, С++, java или другом общем объектно-ориентированном языке, объектно-ориентированное программирование в R может быть довольно запутанным. Следуя духу примеров кода Rosetta в книге, здесь я сравниваю код, который создает и использует класс в python для кода, который создает и использует класс в R.

Первый уровень путаницы состоит в том, что R имеет несколько различных систем для объектно-ориентированного программирования - S3, S4 и R5. Первое решение, которое стоит перед собой, - это то, какой из них выбрать для вашего проекта. S3 был самым длинным и широко используется. Его функциональность ограничена в некоторых ключевых аспектах, но у программиста довольно много гибкости в том, как кодировать классы. S4, более новая система, устраняет некоторые ограничения S3. Это немного сложнее и жестко кодировать, но в конечном итоге более эффективно использовать. В общем, люди используют S3 при работе над существующим кодом, который уже имеет объекты S3, и S4 при внедрении нового кода с нуля. Например, многие новые пакеты биокондукторов написаны на S4. Hadley Wickham имеет отличную сводку S3, S4 и R5, среди других аспектов R, которые являются отличным местом, чтобы больше узнать о объектно-ориентированном программировании в R.

Здесь я фокусируюсь на системе S4.

Ниже приведено определение простого класса Circle в python. Он имеет конструктор __init__() для установки значений при создании новых экземпляров, некоторых методов для установки значений, некоторых методов получения значений и метода модификации экземпляра класса путем вычисления диаметра из радиуса.

class Circle:

    ## Contents
    radius = None
    diameter = None

    ## Methods
    # Constructor for creating new instances
    def __init__(self, r):
        self.radius = r

    # Value setting methods
    def setradius(self, r):
        self.radius = r

    def setdiameter(self, d):
        self.diameter = d

    # Value getting methods
    def getradius(self):
        return(self.radius)

    def getdiameter(self):
        return(self.diameter)

    # Method that alters a value
    def calc_diameter(self):
        self.diameter = 2 * self.radius

Как только вы создали этот класс, создание и использование экземпляра (в ipython) выглядит следующим образом:

In [3]: c = Circle()

In [4]: c.setradius(2)

In [5]: c.calc_diameter()

In [6]: c.getradius()
Out[6]: 2

In [7]: c.getdiameter()
Out[7]: 4

Функция Circle() создает новый экземпляр класса Circle с помощью конструктора, определенного __init__(). Мы используем метод .setradius() для установки значения радиуса и метод .calc_diameter() для вычисления диаметра от радиуса и обновления значения диаметра в экземпляре класса. Затем мы используем методы, которые мы построили для получения значений радиуса и диаметра. Разумеется, мы могли бы также напрямую получить доступ к значениям радиуса и диаметра, используя те же точечные обозначения, которые мы использовали для вызова функций:

In [8]: c.radius
Out[8]: 2

In [9]: c.diameter
Out[9]: 4

Как и в С++, java и многих других общих языках, оба метода и переменные данных являются атрибутами класса. Кроме того, методы имеют прямой доступ для чтения и записи к атрибутам данных. В этом случае метод .calc_diameter() заменил значение диаметра на новое значение, не требуя ничего что-либо изменить для экземпляра класса.

Теперь для объектов S4 в R, которые очень, очень разные. Вот аналогичный класс круга в R:

setClass(
    Class = "Circle", 
    representation = representation(
        radius = "numeric", 
        diameter = "numeric"
    ),
)

# Value setting methods
# Note that the second argument to a function that is defined with setReplaceMethod() must be named value
setGeneric("radius<-", function(self, value) standardGeneric("radius<-"))
setReplaceMethod("radius", 
    "Circle", 
    function(self, value) {
        [email protected] <- value
        self
    }
)

setGeneric("diameter<-", function(self, value) standardGeneric("diameter<-"))
setReplaceMethod("diameter", 
    "Circle", 
    function(self, value) {
        [email protected] <- value
        self
    }
)

# Value getting methods
setGeneric("radius", function(self) standardGeneric("radius"))
setMethod("radius", 
    signature(self = "Circle"), 
    function(self) {
        [email protected]
    }
)

setGeneric("diameter", function(self) standardGeneric("diameter"))
setMethod("diameter", 
    signature(self = "Circle"), 
    function(self) {
        [email protected]
    }
)


# Method that calculates one value from another
setGeneric("calc_diameter", function(self) { standardGeneric("calc_diameter")})
setMethod("calc_diameter", 
    signature(self = "Circle"), 
    function(self) {
        [email protected] <- [email protected] * 2
        self
    }
)

Как только вы создали этот класс, создание и использование экземпляра (в интерактивной консоли R) выглядит следующим образом:

> a <- new("Circle")
> radius(a) <- 2
> a <- calc_diameter(a)
> radius(a)
[1] 2
> diameter(a)
[1] 4

В вызове new("Circle") создан новый экземпляр класса Circle, который мы назначили переменной с именем a. Строка radius(a)<- 2 создала копию объекта a, обновила значение радиуса до 2 и затем указала на новый обновленный объект. Это было выполнено с помощью метода radius<-, определенного выше.

Мы определили calc_diameter() как метод для класса Circle, но обратите внимание, что мы НЕ называем его как атрибут класса. То есть мы не используем синтаксис типа a.calc_diameter(). Вместо этого мы вызываем calc_diameter() так же, как и любую другую автономную функцию, и передаем объект методу в качестве первого аргумента.

Кроме того, мы не просто вызывали calc_diameter(a), мы возвращали результат обратно на a. Это связано с тем, что объекты в R передаются в функции как значения, а не ссылки. Функция получает копию объекта, а не исходного объекта. Затем эта копия управляется внутри функции, и если вы хотите, чтобы измененный объект вернулся, вам нужно сделать две вещи. Во-первых, объект должен быть выполнен в последней строке функции (отсюда одиночные строки self в определениях метода). В R это похоже на вызов return(). Во-вторых, вам нужно скопировать обновленное значение обратно в нашу переменную объекта при вызове метода. Вот почему полная строка a <- calc_diameter(a).

Вызов radius(a) и diameter(a) выполняет методы, которые мы определили для возврата этих значений.

Вы также можете напрямую обращаться к атрибутам данных объекта в R так же, как вы можете использовать объект в python. Вместо использования точечной нотации вы используете обозначение @:

> [email protected]
[1] 2
> [email protected]
[1] 4

В R атрибуты данных называются "слотами". Синтаксис @ дает вам доступ к этим атрибутам данных. Но как насчет методов? В отличие от python, в методах R не являются атрибутами объектов, они определяются setMethod() для действий над конкретными объектами. Класс, на который действует метод, определяется аргументом signature. Могут быть более одного метода с одним и тем же именем, хотя каждый из них действует на разные классы. Это связано с тем, что вызванный метод не просто зависит от имени метода, но также зависит от типа аргументов. Другим примером является метод plot(). Пользователю кажется, что есть одна функция plot(), но на самом деле существует много методов plot(), каждый из которых относится к определенному классу. Вызываемый метод зависит от класса, который передается в plot().

Это попадает в строки setGeneric() в определении класса. Если вы определяете новый метод с использованием уже существующего имени (например, plot()), он вам не нужен. Это связано с тем, что setMethod() определяет новые версии существующих методов. В новых версиях используется другой набор типов данных, а затем существующие версии с тем же именем. Если вы определяете функцию с новым именем, вы должны сначала объявить эту функцию. setGeneric() выполняет эту декларацию, создавая то, что по существу является заполнителем, который вы затем быстро отвергаете.

Различия между классами в python и R не просто косметические, при капоте происходит совсем другое дело, и классы используются по-разному на каждом языке. Тем не менее, есть несколько вещей, которые особенно расстраивают создание и использование классов в R. Создание классов S4 в R требует гораздо более типизации, и большая часть его избыточна (например, каждое имя метода должно быть указано три раза в приведенном выше примере). Поскольку методы R могут получить доступ только к атрибутам данных, сделав копию всего объекта, может быть большой удар производительности даже для простых манипуляций, когда объекты становятся большими. Эта проблема усугубляется для методов, которые изменяют объекты, поскольку данные должны быть скопированы один раз на пути внутрь, а затем один раз на выходе. Эти проблемы, вероятно, способствовали недавнему быстрому росту популярности инструментов python для численного анализа, например pandas. Тем не менее, R остается мощным инструментом, он хорошо подходит для многих распространенных проблем, а богатая экосистема библиотек R необходима для многих анализов.

Ответ 3

Я исхожу из мира Python и в начале было трудно думать с точки зрения R классов. Наконец, я сделал это. Я думаю.

Мне удалось преобразовать классы Python и Java в S4 в R.

Этот пакет rODE был создан с конкретной целью помочь начальному обучению классов S4. Пакет состоит в решении обыкновенных дифференциальных уравнений и содержит немного всего о классах S4.

Надеюсь, это поможет. Ниже ссылки:

https://github.com/f0nzie/rODE

Пакет также находится в CRAN.

Теперь, на ответ. Есть много способов сделать в R, что вы хотите. Вот первый способ, используя классы S4 и прототип, чтобы инициализировать значение ваших переменных A и B.

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    ),
    prototype = prototype(
        A = 1,
        B = 2
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})


# instantiate a class
mc <- new("MyClass")

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

Второй способ сделать это - использовать метод initialize.

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    [email protected] <- 1
    [email protected] <- 2
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})


# instantiate a class
mc <- new("MyClass")

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

[email protected]       # get value on slot A
[email protected]       # get value on slot B

Третий способ сделать это - использовать конструктор и инициализировать переменные класса вне класса с помощью функции-конструктора:

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})


# constructor function
MyClass <- function() {
    myclass <- new("MyClass")
    [email protected] <- 1            # assignment
    [email protected] <- 2
    return(myclass)           # return the class initialized
}

# instantiate a class
mc <- MyClass()

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

[email protected]       # get value on slot A
[email protected]       # get value on slot B

У этого еще есть возможности для улучшения, поскольку мы не должны иметь дело с сырым именем слотов вне класса. Инкапсуляция, помнишь? Вот четвертый и лучший способ использования setReplaceMethod:

setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))
setGeneric("getA", function(object, ..., value) standardGeneric("getA"))
setGeneric("getB", function(object, ..., value) standardGeneric("getB"))
setGeneric("setA<-", function(object, ..., value) standardGeneric("setA<-"))
setGeneric("setB<-", function(object, ..., value) standardGeneric("setB<-"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})

setMethod("getA", "MyClass", function(object, ...) {
    return([email protected])
})

setMethod("getB", "MyClass", function(object, ...) {
    return([email protected])
})

setReplaceMethod("setA", "MyClass", function(object, ..., value) {
   [email protected] <- value
   object
})

setReplaceMethod("setB", "MyClass", function(object, ..., value) {
   [email protected] <- value
   object
})

# constructor function
MyClass <- function() {
    myclass <- new("MyClass")
    return(myclass)           # return the class initialized
}

# instantiate a class
mc <- MyClass()

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

setA(mc) <- 1
setB(mc) <- 2

getA(mc)       # get value on slot A
getB(mc)       # get value on slot B

И пятый способ - создать метод класса, чтобы создать экземпляр самого класса, который полезен для проверки ввода даже для отсутствующих параметров:

.MyClass <- setClass("MyClass", slots = c(
    A = "numeric",
    B = "numeric"
    )
)

# generic functions
setGeneric("hello", function(object, ...) standardGeneric("hello"))
setGeneric("goodbye", function(object, ...) standardGeneric("goodbye"))
setGeneric("ohDear", function(object, ...) standardGeneric("ohDear"))
setGeneric("getA", function(object, ..., value) standardGeneric("getA"))
setGeneric("getB", function(object, ..., value) standardGeneric("getB"))
setGeneric("setA<-", function(object, ..., value) standardGeneric("setA<-"))
setGeneric("setB<-", function(object, ..., value) standardGeneric("setB<-"))
setGeneric("MyClass", function(A, B, ...) standardGeneric("MyClass"))

# Methods of the class
setMethod("initialize", "MyClass", function(.Object, ...) {
    return(.Object)
})

# Methods of the class
setMethod("hello", "MyClass", function(object, ...) {
    return("Hello")
})    

setMethod("goodbye", "MyClass", function(object, ...) {
    return("Goodbye")
})

setMethod("ohDear", "MyClass", function(object, ...) {
    return("Have no clue")
})

setMethod("getA", "MyClass", function(object, ...) {
    return([email protected])
})

setMethod("getB", "MyClass", function(object, ...) {
    return([email protected])
})

setReplaceMethod("setA", "MyClass", function(object, ..., value) {
   [email protected] <- value
   object
})

setReplaceMethod("setB", "MyClass", function(object, ..., value) {
   [email protected] <- value
   object
})

setMethod("MyClass", signature(A="numeric", B="numeric"), function(A, B, ...) {
    myclass <- .MyClass()
    [email protected] <- A
    [email protected] <- B
    return(myclass)
})

# instantiate the class with values
mc <- MyClass(A = 1, B = 2)

# use the class methods
hello(mc)
goodbye(mc)
ohDear(mc)

getA(mc)       # get value on slot A
getB(mc)       # get value on slot B