22

私は Python プログラマーで、R を扱うのは今日が初めてです。

コンストラクターと 3 つのメソッドを使用してクラスを作成しようとしていますが、苦労しています。

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"

Rでこれほど簡単なことをする方法を示すものは何も見つかりません。誰かがこれを行う方法を1つ教えていただければ幸いです。

どうもありがとう

4

3 に答える 3

37

Rには、実際には多くの異なるオブジェクト指向の実装があります。(@jthetzelで言及されているように)3つのネイティブクラスは、S3、S4、および参照クラスです。

S3は、最初の引数のクラスに基づいて関数をオーバーロードできる軽量システムです。

参照クラスは、他のプログラミング言語のクラスにより類似するように設計されています。それらは多かれ少なかれS4クラスに取って代わります。S4クラスは同じことをしますが、より扱いにくい方法で行います。

R.ooパッケージは別のシステムを提供し、protoパッケージは軽量OOPのようなプロトタイプ指向プログラミングを可能にします。OOPパッケージには6番目のシステムがありましたが、現在は機能していません。最近のR6パッケージは、「Rの組み込み参照クラスに代わる、より単純で、より高速で、より軽量なものです」

新しいプロジェクトの場合、通常はS3クラスとReferenceクラス(または場合によってはR6)のみを使用する必要があります。

Pythonクラスは、最も簡単に参照クラスに変換されます。それらは比較的新しく、(ジョン・チェンバースがそれらについての本を書き終えるまで)最良の参照は?ReferenceClassesページです。始めるための例を次に示します。

クラスを定義するには、を呼び出しますsetRefClass。最初の引数はクラスの名前であり、慣例により、これは結果を割り当てる変数と同じである必要があります。listまた、引数「fields」と「methods」にsを渡す必要があります。

いくつかの癖があります。

  • フィールドに含める必要のある変数タイプを指定したくない場合は、フィールドのリストの値として「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!")

さらに読む:AdvancedRのOOフィールドガイドの章。

于 2012-07-19T13:57:12.987 に答える
11

私は最近、Python クラスと R S4 クラスの比較を書きました。

http://practicalcomputing.org/node/80

R と python では、クラスの宣言方法、使用方法、動作方法の両方において、クラスが大きく異なります。

コメントでの mbinette の要求に従って、投稿の全文を以下に示します (私は 2 つの権限しか持っていないため、ほとんどのハイパーリンクを除いています)。

Python、C++、Java、またはその他の一般的なオブジェクト指向言語でプログラミングしたことがある人にとって、R でのオブジェクト指向プログラミングは非常に混乱する可能性があります。本書の Rosetta コード例の精神に従い、Python でクラスを作成して使用するコードと、R でクラスを作成して使用するコードを比較します。

混乱の最初の層は、R がオブジェクト指向プログラミング用にいくつかの異なるシステム (S3、S4、および R5) を持っていることです。直面する最初の決定は、これらのどれをプロジェクトに選択するかです。S3 は最も長く使用されており、広く使用されています。その機能はいくつかの重要な点で制限されていますが、プログラマーはクラスのコーディング方法にかなりの柔軟性を持っています。新しいシステムである S4 は、S3 のいくつかの制限に対処しています。コードは少し複雑で厳格ですが、最終的にはより強力に使用できます。一般に、S3 オブジェクトが既に存在する既存のコードで作業する場合は S3 を使用し、新しいコードをゼロから実装する場合は S4 を使用します。たとえば、多くの新しいバイオコンダクタ パッケージは、S4 で記述されています。Hadley Wickham は、 S3、S4、および R5の優れた要約を持っています。、R の他の側面の中でも特に、R でのオブジェクト指向プログラミングについて学習するのに最適な場所です。

ここでは、S4 システムに焦点を当てます。

以下は、Python での単純な Circle クラスの定義です。新しいインスタンスが作成されるときに値を設定するための__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

C++、Java、およびその他の多くの一般的な言語と同様に、メソッドとデータ変数の両方がクラスの属性です。さらに、メソッドには、データ属性への直接の読み取りおよび書き込みアクセスがあります。この場合、.calc_diameter()メソッドは直径の値を新しい値に置き換えましたが、クラス インスタンスに関して他に何も変更する必要はありません。

次に、R の S4 オブジェクトについて説明します。これは非常に大きく異なります。R の同様の Circle クラスを次に示します。

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) {
        self@radius <- value
        self
    }
)

setGeneric("diameter<-", function(self, value) standardGeneric("diameter<-"))
setReplaceMethod("diameter", 
    "Circle", 
    function(self, value) {
        self@diameter <- value
        self
    }
)

# Value getting methods
setGeneric("radius", function(self) standardGeneric("radius"))
setMethod("radius", 
    signature(self = "Circle"), 
    function(self) {
        self@radius
    }
)

setGeneric("diameter", function(self) standardGeneric("diameter"))
setMethod("diameter", 
    signature(self = "Circle"), 
    function(self) {
        self@diameter
    }
)


# Method that calculates one value from another
setGeneric("calc_diameter", function(self) { standardGeneric("calc_diameter")})
setMethod("calc_diameter", 
    signature(self = "Circle"), 
    function(self) {
        self@diameter <- self@radius * 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 のコピーを作成し、radius の値を 2 に更新してから、a が新しく更新されたオブジェクトを指すようにしました。これは、radius<-上で定義した方法で達成されました。

calc_diameter()クラスのメソッドとして定義しましたCircleが、クラスの属性であるかのように呼び出さないことに注意してください。つまり、 のような構文は使用しませんa.calc_diameter()。代わりに、calc_diameter()他のスタンドアロン関数と同じように呼び出し、オブジェクトを最初の引数としてメソッドに渡します。

さらに、 を呼び出すだけでなくcalc_diameter(a)、出力を に割り当てましたa。これは、R のオブジェクトが参照ではなく値として関数に渡されるためです。この関数は、元のオブジェクトではなく、オブジェクトのコピーを取得します。そのコピーは関数内で操作されます。変更されたオブジェクトを元に戻したい場合は、2 つのことを行う必要があります。まず、関数の最終行でオブジェクトを実行する必要があります (そのためself、メソッド定義の行が孤立しています)。R では、これは を呼び出すようなものreturn()です。次に、メソッドを呼び出すときに、更新された値をオブジェクト変数にコピーする必要があります。これが、フルラインがa <- calc_diameter(a).

radius(a)とのdiameter(a)呼び出しは、これらの値を返すために定義したメソッドを実行します。

Python のオブジェクトと同じように、R のオブジェクトのデータ属性に直接アクセスすることもできます。ただし、ドット表記を使用する代わりに、次の@表記を使用します。

> a@radius
[1] 2
> a@diameter
[1] 4

R では、データ属性は「スロット」と呼ばれます。この@構文により、これらのデータ属性にアクセスできます。しかし、メソッドはどうですか?Python とは異なり、R のメソッドはオブジェクトの属性ではなく、setMethod()特定のオブジェクトに作用するように定義されています。メソッドが作用するクラスは、signature引数によって決定されます。ただし、同じ名前の複数のメソッドが存在する可能性があり、それぞれが異なるクラスに作用します。これは、呼び出されるメソッドがメソッドの名前だけでなく、引数の型にも依存するためです。おなじみの例はメソッドplot()です。ユーザーには 1 つの関数のように見えますplot()が、実際にはplot()、特定のクラスに固有のメソッドが多数存在します。plot().

これによりsetGeneric()、クラス定義の行に到達します。既存の名前 ( などplot()) を使用して新しいメソッドを定義する場合は、その必要はありません。これはsetMethod()、既存のメソッドの新しいバージョンを定義するためです。新しいバージョンは、同じ名前の既存のバージョンとは異なるデータ型のセットを取ります。ただし、新しい名前で関数を定義する場合は、最初に関数を宣言する必要があります。setGeneric()はこの宣言を処理し、本質的にプレースホルダーを作成します。これをすぐにオーバーライドします。

Python と R のクラスの違いは表面的なものだけではなく、内部で非常に異なることが起こっており、各言語でクラスが異なる方法で使用されています。ただし、R でクラスを作成して使用する際に特にイライラすることがいくつかあります。R で S4 クラスを作成するには、より多くの入力が必要であり、その多くは冗長です (たとえば、上記の例では、各メソッド名を 3 回指定する必要がありました)。R メソッドは、オブジェクト全体のコピーを作成することによってのみデータ属性にアクセスできるため、オブジェクトが大きくなると、単純な操作でも大きなパフォーマンス ヒットが発生する可能性があります。この問題は、オブジェクトを変更するメソッドの場合に複雑になります。データは、途中で 1 回コピーし、次に途中で 1 回コピーする必要があるためです。これらの問題は、pandas などの数値解析用の Python ツールの人気が最近急速に高まっている一因であると考えられます。とはいえ、R は依然として強力なツールであり、多くの一般的な問題に適しています。R ライブラリの豊富なエコシステムは多くの分析に不可欠です。

于 2012-11-25T06:17:28.327 に答える