5

私は R に足を踏み入れたばかりで、関数がオブジェクトを変更しないことに驚きました。少なくともそれがデフォルトのようです。たとえば、テーブルの 1 つのラベルにアスタリスクを付けるだけの関数を書きました。関数内で機能しますが、テーブル自体は変更されません。(私は主に Ruby から来ています)

では、関数を使用して R のオブジェクトを変更する通常の受け入れられている方法は何ですか? テーブルのタイトルにアスタリスクを追加するにはどうすればよいですか?

4

3 に答える 3

26

問題が発生している理由は、オブジェクトを関数のローカル名前空間に渡しているためです。これは、Rの優れた/ひどいことの1つです。暗黙的な変数宣言を許可し、名前空間が深くなるにつれて優先順位を実装します。

関数が現在の名前空間内に新しい名前空間を作成するため、これは影響を及ぼします。オブジェクト「myTable」は、もともとグローバル名前空間で作成されたと思いますが、関数「title.asterisk」に渡されると、新しい関数ローカル名前空間に同じプロパティを持つオブジェクトが含まれるようになります。これは次のように機能します。

title.asterisk <- function(myTable){ do some stuff to 'myTable' }

この場合、関数'title.asterisk'はグローバルオブジェクト'myTable'に変更を加えません。代わりに、ローカルオブジェクトが同じ名前で作成されるため、ローカルオブジェクトがグローバルオブジェクトに優先します。このように関数を呼び出すと、関数title.asterisk(myTable)はローカル変数にのみ変更を加えます。

グローバルオブジェクトを変更するには、2つの直接的な方法(および多くの間接的な方法)があります。

オプション1:最初に、あなたが言及したように、次のように、関数にオブジェクトを返し、グローバルオブジェクトを上書きさせることです。

title.asterisk <- function(myTable){
    do some stuff to 'myTable'
    return(myTable)
}
myTable <- title.asterisk(myTable)

これは問題ありませんが、実際には2つの異なる「myTable」オブジェクトがあり、1つは関数に対してグローバルで、もう1つはローカルであるため、コードを理解するのが少し難しくなっています。多くのコーダーは、ピリオド「。」を追加することでこれを明確にします。次のように、可変引数の前に:

title.asterisk <- function(.myTable){
    do some stuff to '.myTable'
    return(.myTable)
}
myTable <- title.asterisk(myTable)

これで、2つの変数が異なるという視覚的な手がかりが得られました。後でコードをデバッグしようとするときに、名前空間の優先順位などの目に見えないものに依存したくないので、これは良いことです。それは物事を必要以上に難しくします。

オプション2:関数内からオブジェクトを変更するだけです。これは、オブジェクトに破壊的な編集を行い、メモリを膨らませたくない場合に適したオプションです。破壊的な編集を行う場合は、元のコピーを保存する必要はありません。また、オブジェクトが適切に大きい場合は、必要のないときにコピーする必要はありません。グローバル名前空間オブジェクトを編集するには、関数に渡したり、関数内から宣言したりしないでください。

title.asterisk <- function(){ do some stuff to 'myTable' }

現在、関数内からオブジェクト'myTable'を直接編集しています。オブジェクトを渡していないという事実により、関数は変数名を解決するために、より高いレベルの名前空間を参照します。見よ、それは「myTable」オブジェクトをより高い位置で見つけます!関数のコードは、オブジェクトに変更を加えます。

考慮すべき注意:私はデバッグが嫌いです。つまり、デバッグは本当に嫌いです。これは、Rで私にとっていくつかのことを意味します。

  • ほとんどすべてを関数でラップします。コードを書くとき、作品が機能するようになったらすぐに、それを関数でラップして脇に置きます。私は「。」を多用します。すべての関数引数にプレフィックスを付け、それが存在する名前空間にネイティブなものにはプレフィックスを使用しません。
  • 関数内からグローバルオブジェクトを変更しないようにしています。私はこれがどこにつながるのか好きではありません。オブジェクトを変更する必要がある場合は、それを宣言した関数内から変更します。これは多くの場合、関数を呼び出す関数のレイヤーがあることを意味しますが、モジュール式で理解しやすいものになります。
  • すべてのコードにコメントし、各行またはブロックの目的を説明します。少し無関係に思えるかもしれませんが、これら3つのことが私にとってはうまくいくことがわかりました。コーディングを関数でラップし始めると、古いコードをもっと再利用したいと思うようになります。そこで良いコメントが入ります。私にとって、それは必要な部分です。
于 2013-03-19T12:09:37.987 に答える
10

あなたが示すように、2つのパラダイムはオブジェクト全体を置き換えるか、次のような「置換」関数を記述します

`updt<-` <- function(x, ..., value) {
    ## x is the object to be manipulated, value the object to be assigned
    x$lbl <- paste0(x$lbl, value)
    x
}

> d <- data.frame(x=1:5, lbl=letters[1:5])
> d
  x lbl
1 1   a
2 2   b
3 3   c
> updt(d) <- "*"
> d
  x lbl
1 1  a*
2 2  b*
3 3  c*

これは、たとえば、$<--- によってアクセスされる要素をインプレース更新する場合の動作です$ここに関連する質問があります。置換関数は、構文糖衣と考えることができます。

updt1 <- function(x, ..., value) {
    x$lbl <- paste0(x$lbl, value)
    x
}
d <- updt1(d, value="*")

しかし、「シンタックス シュガー」というラベルは、関係する中心的なパラダイムに対して、私の考えでは実際には正当化されません。これは、R が通常保持しているコピー オン チェンジの錯覚とは異なり、便利なインプレース更新を可能にしてい?ReferenceClassesます。他の言語のように感じますが、変更時のコピーのセマンティクスを期待している R ユーザーを驚かせるでしょう)。

于 2013-03-19T11:29:05.203 に答える
1

将来、これを解決するための簡単な方法を探している人のために(それがより適切な方法であるかどうかはわかりません):

関数内でオブジェクトを作成し、変更したいオブジェクトの修正バージョンを一時的に保存します。deparse(substitute())関数の引数に渡された変数の名前を取得するために使用しassign()、オブジェクトを上書きするために使用します。関数の外側の環境でオブジェクトを定義できるようにするには、 envir = parent.frame()insideを使用する必要があります。assign()

(MyTable <- 1:10)

[1] 1 2 3 4 5 6 7 8 9 10

title.asterisk <- function(table) {
  tmp.table <- paste0(table, "*")
  name      <- deparse(substitute(table))
  assign(name, tmp.table, envir = parent.frame())
}

(title.asterisk(MyTable))

[1] 「1*」「2*」「3*」「4*」「5*」「6*」「7*」「8*」「9*」「10*」

オブジェクトを定義するときに括弧を使用すると、定義してから印刷するよりも少し効率的です (そして私にとっては見栄えが良くなります)。

于 2018-12-02T09:21:50.733 に答える