web-dev-qa-db-ja.com

`levels <-`(これはどんな魔術ですか?

別の質問への回答で、@ Marekは次のソリューションを投稿しました: https://stackoverflow.com/a/10432263/636656

dat <- structure(list(product = c(11L, 11L, 9L, 9L, 6L, 1L, 11L, 5L, 
                                  7L, 11L, 5L, 11L, 4L, 3L, 10L, 7L, 10L, 5L, 9L, 8L)), .Names = "product", row.names = c(NA, -20L), class = "data.frame")

`levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

これは出力として生成されます:

 [1] Generic Generic Bayer   Bayer   Advil   Tylenol Generic Advil   Bayer   Generic Advil   Generic Advil   Tylenol
[15] Generic Bayer   Generic Advil   Bayer   Bayer  

これは単なるベクターの出力なので、それを保存することで、さらに混乱を招く可能性があります。

res <- `levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )

明らかにこれはレベル関数へのある種の呼び出しですが、私はここで何が行われているのか分かりません。この種の魔術の用語は何ですか?この領域で私の魔法の能力をどのように増加させるのですか?

112
Ari B. Friedman

ここでの回答は良いですが、重要な点が欠けています。それについて説明してみましょう。

Rは関数型言語であり、そのオブジェクトを変更したくない。しかし、置換関数を使用して、代入ステートメントを許可します。

levels(x) <- y

に相当

x <- `levels<-`(x, y)

コツは、この書き直しが<-によって行われることです。 levels<-はこれを行いません。 levels<-は、入力を受け取り、出力を提供する通常の関数です。何も変化しません。

その結果の1つは、上記のルールに従って、<-は再帰的でなければならないということです。

levels(factor(x)) <- y

です

factor(x) <- `levels<-`(factor(x), y)

です

x <- `factor<-`(x, `levels<-`(factor(x), y))

この純粋な関数型変換(割り当てが発生する最後まで)が、命令型言語での割り当てと同等であることは、一種の美しさです。私が正しく覚えている場合、関数型言語でのこの構造はレンズと呼ばれます。

しかし、levels<-のような置換関数を定義すると、別の予期しない結果が生じます。割り当てを行うだけの機能はありません。ある要素を取り込んで別の要素を提供する便利な関数があります。異なるレベルの要素。それについての「割り当て」は本当に何もありません!

したがって、あなたが記述しているコードは、levels<-のこの別の解釈を利用しているだけです。名前levels<-は割り当てを示唆しているため、少し混乱していることは認めますが、これは現在の状況ではありません。コードは、一種のパ​​イプラインを設定するだけです。

  • dat$productで始まる

  • それを因子に変換する

  • レベルを変更する

  • resに保存します

個人的には、コード行は美しいと思います;)

101
Owen

ソーサリーはありません。これが(サブ)割り当て関数の定義方法です。 levels<-は、要素自体ではなく、因子の属性を(サブ)に割り当てるためのプリミティブであるため、少し異なります。このタイプの関数の例はたくさんあります:

`<-`              # assignment
`[<-`             # sub-assignment
`[<-.data.frame`  # sub-assignment data.frame method
`dimnames<-`      # change dimname attribute
`attributes<-`    # change any attributes

他の二項演算子も同様に呼び出すことができます:

`+`(1,2)  # 3
`-`(1,2)  # -1
`*`(1,2)  # 2
`/`(1,2)  # 0.5

あなたはそれを知っているので、このようなものは本当にあなたの心を打つはずです:

Data <- data.frame(x=1:10, y=10:1)
names(Data)[1] <- "HI"              # How does that work?!? Magic! ;-)
32
Joshua Ulrich

その「魔法」の理由は、「割り当て」フォームが機能するための実際の変数を持っている必要があるためです。そして、factor(dat$product)は何にも割り当てられていません。

# This works since its done in several steps
x <- factor(dat$product)
levels(x) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
x

# This doesn't work although it's the "same" thing:
levels(factor(dat$product)) <- list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
# Error: could not find function "factor<-"

# and this is the magic work-around that does work
`levels<-`(
  factor(dat$product),
  list(Tylenol=1:3, Advil=4:6, Bayer=7:9, Generic=10:12)
  )
30
Tommy

ユーザーコードの場合、なぜそのような言語操作が使用されるのでしょうか?これはどんな魔法なのかと尋ねると、_levels<-_という名前の置換関数を呼び出していると他の人が指摘しました。ほとんどの人にとってこれは魔法であり、実際の使用目的はlevels(foo) <- barです。

productはグローバル環境に存在せず、_levels<-_の呼び出しのローカル環境にのみ存在するため、表示するユースケースは異なります。したがって、実行したい変更は行われません。持続-datの再割り当てはありませんでした。

このような状況では、within()が理想的な関数です。あなたは自然に書きたいと思うでしょう

_levels(product) <- bar
_

rではもちろんproductはオブジェクトとして存在しません。 within()は、Rコードを実行する環境をセットアップし、その環境内で式を評価するため、これを回避します。したがって、within()の呼び出しからの戻りオブジェクトの割り当ては、適切に変更されたデータフレームで成功します。

ここに例があります(新しいdatXを作成する必要はありません-中間ステップが最後に残るように、私はそれを行うだけです)

_## one or t'other
#dat2 <- transform(dat, product = factor(product))
dat2 <- within(dat, product <- factor(product))

## then
dat3 <- within(dat2, 
               levels(product) <- list(Tylenol=1:3, Advil=4:6, 
                                       Bayer=7:9, Generic=10:12))
_

それは与える:

_> head(dat3)
  product
1 Generic
2 Generic
3   Bayer
4   Bayer
5   Advil
6 Tylenol
> str(dat3)
'data.frame':   20 obs. of  1 variable:
 $ product: Factor w/ 4 levels "Tylenol","Advil",..: 4 4 3 3 2 1 4 2 3 4 ...
_

私が示すような構成が大部分の場合にどのように役立つかを確認するのに苦労しています-データを変更したい場合は、データを変更し、別のコピーを作成せずに変更してください(これはすべて_levels<-_です)結局のところ電話はやっている)。