niszetの日記

細かい情報を載せていくブログ

(R) formulaをView()してみてみる(2回目)symbolの話も。

symbolはベクトルを作れない

既に下記の記事やっていた1のだが、 niszet.hatenablog.com

単純に、c()でベクトルを作ろうと思う。symbolas.symbol()で作る。

c(as.symbol("~"), as.symbol("a"))
[[1]]
`~`

[[2]]
a

構造を見てみると、

str(c(as.symbol("~"), as.symbol("a")))
#> List of 2
#>  $ : symbol ~
#>  $ : symbol a

is.list(c(as.symbol("~"), as.symbol("a")))
#> [1] TRUE

で、listになっている、と。ようやく理解できたかな…。

languageを直接作る方法はなさそう

さて、前回はformulaを分解してみていきました。

niszet.hatenablog.com

formulaはformula()as.formula()によって作ることができます。他にもa~bのような形で作れますが、後者の書式だとsymbolで書かねばならないので、文字列からas.formula()で作った方が関数等に使いまわしがしやすいかな。

rlangパッケージやplyrパッケージも見てみましたが、as.language()みたいな関数はなさそうです。が、

quote(a~b)
plyr::is.formula(quote(a~b))
#> [1] FALSE
is.language(quote(a~b))
#> [1] TRUE

quoteでlanguageとなります。この辺りはまた別にわけよう。

また、as.formula()の引数にあるように、formulaは環境(environment)を必要とします。前回見たように、languageenvironmentがついた構造がformulaです(class属性も持っているけど)

で、language~symbolで構成されて、1番目の要素は必ず~、そのあと左辺と右辺に相当するlanguagesymbolが入り、languageの場合は同じルールでネストすることが可能ということですね。前回見た通り。

ちなみに、

x<-as.formula("a~b")
x[[2]]<-as.formula("b~c")

で要素を置き換えることが出来るが、置き換えた部分はlanguageではなくformulaになってしまうのである。単体のlanguageオブジェクトを作って置き換える場合はquote()で。
あと、一旦formulaの形をとると、なんでも代入できる気がする。文字列"a~b"も関数sumも入った。View()で確認するとちゃんとそれぞれのオブジェクトのまま(languageとかsymbolにならないで)格納されていることがわかる。これはlanguageでも同じで、これはNSEにつながる話のようだな。

また、as.formula()にNULLを渡すとlistかつformulaとなるようだ。

x<-as.formula(NULL)
x
#> list()
is.list(x)
#> [1] TRUE
plyr::is.formula(x)
#> [1] TRUE

# 当然、通常の作り方の場合はlistではない。
is.list(a~b)
#> [1] FALSE
plyr::is.formula(x)
#> [1] TRUE

未評価のままオブジェクトを保存できる便利な箱の出来上がりですね。

symbolはシンボリックリンクみたいなもん。

symbolは変数の名前であって、中身は入っていないリンクのようなもので、リンク元を環境という形で与えられると値を取り出すことが出来る。

見た方が分かりやすくて、

a<- 1
x<-new.env()
x$a <- 1000
eval(as.symbol("a"))
#> [1] 1
eval(as.symbol("a"), envir = x)
#> [1] 1000

のように、適当な環境に変数aをその実体としては1であるように作っておくと、その環境のもとでeval()すればその環境での変数名から値を取り出すことが出来る、そんな感じ。

調べると調べた分だけ不明な点が出てくるので文章をまとめてから日記に出来ていませんが…。このあたり全容を把握出来たらもう一度清書したい…

Enjoy!!


  1. 教えていただいただけで僕何もしていないのでは。

(R) formulaをView()してみてみる(1回目)

quote, substitute, language, symbol, name, expression, formula, quo, enquo あたりを見ていく予定。長くなりそう。

これの続き。 niszet.hatenablog.com

実際に見てみる。

たとえば、こういう感じである。

View(a~b~c~d)

f:id:niszet:20180401220251p:plain

a~b~c~dformulaであるが、階層構造となっていて~, a~b~c, dに分解されている。a~b~cはさらに分解されて、最終的には~, a, b に分解される。

また、formulaattributesを持っていて、classとしてformula、そして.Environment属性として、環境を持つ。この環境はR_GlobalEnvであることがわかる(この環境はおそらく常に作成された環境となるはず。次回以降見てみる。)

attr((a~b~c~d) ,".Environment")
#> <environment: R_GlobalEnv>

実際に、こんな感じに書けば、

(a~b~c~d)[[2]][[2]][[2]]
#> a

aを取り出すことも可能。a. b. c, d~はそれぞれsymbolで、

is.symbol((a~b~c~d)[[2]][[2]][[2]])
#> [1] TRUE

となる。symbolnameともいわれるが、それは

mode((a~b~c~d)[[2]][[2]][[2]])
#> [1] "name"

で、

is.name((a~b~c~d)[[2]][[2]][[2]])
#> [1] TRUE

である。

これがsymbolであることを値として取り出すなら、

typeof((a~b~c~d)[[2]][[2]][[2]])
#> [1] "symbol"

である。str()でも見れる。

str((a~b~c~d)[[2]][[2]][[2]])
#> symbol a

なお、aの取り出し時に[[ではなく[で抜き出すと

 (a~b~c~d)[[2]][[2]][2]
#> a()

のようになって、もはやsymbolではない

is.symbol((a~b~c~d)[[2]][[2]][2])
#> [1] FALSE

これはcallableなオブジェクトである。

is.call((a~b~c~d)[[2]][[2]][2])
#> [1] TRUE
mode((a~b~c~d)[[2]][[2]][2])
#> [1] "call"

具体的にはlanguageである。

typeof((a~b~c~d)[[2]][[2]][2])
#> [1] "language"
is.language((a~b~c~d)[[2]][[2]][2])
#> [1] TRUE

ちなみに、languageformulaではない。

plyr::is.formula((a~b~c~d)[[2]][[2]][2])
#> [1] FALSE

ただし、formulalanguageである1

plyr::is.formula((a~b~c~d))
#> [1] TRUE
is.language((a~b~c~d))
#> [1] TRUE

また、[[でアクセスできるからと言って、listでもない

is.list((a~b~c~d))
#> [1] FALSE
is.list((a~b~c~d)[[2]][[2]][2])
#> [1] FALSE

基本的なことは大体見れたはずなので、もう少しformulaを見て、他の要素も見ていく予定。
長くなってきたのでこのあたりで。

Enjoy!!


  1. たぶん。常に成り立つよね…?

(R) ggplotにlimitsを指定しない時のrangeはどうやって取ってくる?

一旦計算してもらう方が良さそう。

先日のbode plotで、panel.grid.minor.xmissing(xlim)の時は表示されないという問題がありました。
対数軸のgrid.minorは自分で計算しないと(多分)表示されないし、始点と終点が中途半端な数字になっている場合、意図しないところにgridが出てくるためfloor()ceiling()で調整しました。

が、xlimを与えなくても適当に表示してほしいものです。その対応をしました。

どこにあるか

たとえばScaleオブジェクトのget_limits()関数1github.com

RangeContinuousオブジェクトのtrain()関数、 github.com

などはそれっぽい要素ですが、そもそも、ggplotオブジェクトはprint.ggplot()によって描画される直前に座標を含めた表示の調整・計算をしているので、これらのggprotoオブジェクトを追うよりはprint.ggplot()を追いかけた方が良いです。

実際、内部ではggplot_build.ggplot()という関数が呼ばれていて2、ここで処理がされています。 そこで、この関数単体を呼び出すことで、plot前の座標調整された状態の情報が得られるという寸法です。こちらのSOの投稿を参考にしました。

stackoverflow.com

ただ、データ構造がこの投稿当時と異なるようでして、私が欲しい情報は例えば変数gggplot()+geom_line()dataaes()が与えられているものが代入されているとして、ggplot2::ggplot_build(g)[["layout"]][["panel_ranges"]][[1]]$x.rangeによって得ることが出来ました。これ以外の情報も、例によってView()によって追いかけることが容易です3

ただ、前処理部とはいっても、このデータ処理分は全体として遅くなってしまうので注意が必要だと思うのと、このデータ構造はいつ変わるかわからないという点が不安ですね。
あまり内部構造に立ち入った形では使いたくないというのが正直なところです。

まぁ目的は達したので、よしよし。

Enjoy!!


  1. メソッドと呼ぶ方が適切かも。そういう意味だとScaleオブジェクトって呼び方で良いのか?という話もあるので…。

  2. 正確にはメソッドディスパッチがされているので直接呼ばれているのはggplot_build()ですが

  3. それもいずれ書こうかと思いますが、いつになるのかは不明。あくまでniszetさんがとても必要になったときにのみ日記に書いていくスタンスですので…。

(R) メソッドディスパッチは一番前のclassが使われる仕様…?(メモ)

どこかに書いてあったかなぁ…

まぁ大体わかってるんだけど、一応確認してみる。

# クラスがaaやbbだったら内容にかかわらずaa, bbとそれぞれ表示する
print.bb <- function(x) print("bb")
print.aa <- function(x) print("aa")

# まだ存在しない変数
x
#> Error: object 'x' not found

# classを与える前の挙動の確認
x <- 1
print(x)
#> [1] 1

# class aa を与えてみる
class(x) <- "aa"

# 確認
x
#> [1] "aa"

# class bb を与えてみる
class(x) <- "bb"
x
#> [1] "bb"

# aaとbbをこの順で与えてみる。
class(x) <- c("aa", "bb")

# 先に与えているclassの方の関数が呼ばれる(c()内の左側の要素)
x
#> [1] "aa"

# ランダムに結果が変わったりしない。当たり前か
x
#> [1] "aa"

# 逆順にしてみる
class(x) <- c("bb", "aa")

# やはり先に入っているbbの方が有効
x
#> [1] "bb"

# bbの方のprintを消してみる
rm(print.bb)

# 2番目のclassのaaの方のprintが呼ばれている
x
#> [1] "aa"

というわけで、基本的にはdata.frameなんだけど、ある関数では別の挙動をしてほしいようなときには自作のクラス名を先頭につけ、対応する関数を作成しておく(当然、大元が総称関数となっていないといけないけど…)ということですね。

たまに基本的なことが不安になる…(ので確認していく)

追記

yutannihilationさんに教えていただきました。

そのものずばりが載ってる…!

S3 · Advanced R.

一回読んだはずなのに…。やはり読んでわかった気になっているのは危ないですね…。 精進あるのみ💪

Enjoy!!

(R) Extending ggplot2(和訳)を読んでいく…(メモ)

ggbodeパッケージが欲しくて。

先日からggplot2頑張っていき姿勢を示していますが、よく使う設定はもうパッケージにして使いまわしたい…という気持ちから、この記事を読んでstatgeomについて理解をしようとしています。

qiita.com

原文でもがんばれば読めるけど、やっぱり日本語で書かれた資料があるのはありがたいですね…。特に新しい概念を修得するときは母国語で学べるというのは良いことだと思っています。いずれは英語で理解できなくてはいけないとしてもね…。

そして、ようやくstatのところ読み終わった。しかし、compute_groupsetup_paramsがどのタイミングで処理されていくのかがわかっていないと適切に対処できないのでは…?という気持ちがある。まぁこれはいずれ解読する。

geomについてはgridの理解が必要になるということなのでこちらを購入した。いずれ届くだろう。

www.kyoritsu-pub.co.jp

全容を解明するには、print.ggplot()を読んで解読するのがおそらく一番早いのかなと思っているのだけども、まとまった資料が既にあるのであればそれを参照したいなぁ…。
どこかにggplot2パッケージ内部構造徹底解説本ありませんかね…?

theme_bw は完全なtheme

先のExtending ggplot2(和訳)のthemeのところに書かれていますが、theme_bw()は完全なthemeだそうです。曰く「完全なthemeを足し合わせると、既存のthemeを取り除き、新しいthemeを適用します。」とのことで、theme_bw()を使った場合にうまくカスタマイズできないのはこれのせいかな?

ということで、中身を見てみると、

function (base_size = 11, base_family = "") 
{
    theme_grey(base_size = base_size, base_family = base_family) %+replace% 
        theme(panel.background = 
            element_rect(fill = "white", colour = NA), 
            panel.border = element_rect(fill = NA, colour = "grey20"), 
            panel.grid.major = element_line(colour = "grey92"), 
            panel.grid.minor = element_line(colour = "grey92", size = 0.25), 
            strip.background = element_rect(fill = "grey85", colour = "grey20"), 
            legend.key = element_rect(fill = "white", colour = NA), 
            complete = TRUE)
}
<environment: namespace:ggplot2>

最後にcomplete = TRUEがあるので、これがないthemeを作ればよさそう。先日のbode plot用のthemeにちょっと反映させよう。

ということで、引き続き頑張っていくという気持ちの表明。

Enjoy!!

(R) formulaについて調べていた(メモ)

結局、パッケージごとにどう使われているかは調べないといけないようだが…。

最近、ggplot2パッケージの細かいところを調整できるように、過去の記事やヘルプを読みつつ、Rのコードも読み進めているのですが、その中でformulaを使用してラベルを書くことについて調べていて、下記の記事に行き当たった。

qiita.com

他にも、

Math Notation for R Plot Titles: expression, bquote, &amp; Greek&nbsp;Letterstrinkerrstuff.wordpress.com

github.com

Label with mathematical expressions — label_bquote • ggplot2

Special characters in labels

などがあるが、

  1. 文字列でごまかす
  2. UTF-8使って記号をそのまま書く
  3. expression()を使う
  4. bquote()を使う

などがあるようだ。ちょっとまだわかっていないところがあるが…。

formulaがわからん

で、文字をそのまま使う場合以外はformulaについての理解が必要そう。formulaについての日本語の記事ってあまりなくて、id:ill-identified さんのこちらの記事が一番まとまっているのではと思う。

ill-identified.hatenablog.com

それとは別に、この記事

www.datacamp.com

を見つけたので一通り読んでみた。2-3時間かかった気がするが…ひととおり読んでみた。
分かったようなわからんような…。

しかし、冒頭に書いたように、使われ方はパッケージごとに違うので、ラベルの付け方についてはやっぱりggplot2パッケージのソースを読んでいくしかないのかな…という感じです。

まぁ、これは長期戦でやっていきましょう…

Enjoy!!

(R) approxfun 便利だけど、どこに値を隠し持っているのか気になったので調べたメモ

Rで統計以外の計算の情報があまり見つからない気がする…

なんらかの理由によって測定値がなかったりして、付近の値で代用する際などに便利なapproxfun()について、使い方をteramonagiさんのブログで覚えたのですが、

d.hatena.ne.jp

このapproxfun()関数自体は戻り値として関数を返してくるんだけど、一体どこにデータを隠し持ってるんだ…?と思って調べてみたのです。 結果としては環境オブジェクト中に隠し持っていました(別に隠してはいない)

# 毎度おなじみのiris
head(iris$Sepal.Length)
#> [1] 5.1 4.9 4.7 4.6 5.0 5.4

# approxfunに与えると関数が返ってくる。
approxfun(iris$Sepal.Length)
#> function (v) 
#> .approxfun(x, y, v, method, yleft, yright, f)
#> <bytecode: 0x0000000015abe7d8>
#> <environment: 0x00000000080519e0>

# 値をあたえる。1番目の値が返ってくる。
approxfun(iris$Sepal.Length)(1)
#> [1] 5.1

# 補間される。1.2番目相当の値
approxfun(iris$Sepal.Length)(1.2)
#> [1] 5.06

# ちょうど1番目と2番目のまんなかの値
approxfun(iris$Sepal.Length)(1.5)
#> [1] 5

さて、この関数の返却値の関数を直接見てもどこにデータがあるかわかりませんが、先にprintしたときにenvironmentが表示されていますね。これを確認します。つまりこんな感じ。

View(environment(approxfun(iris$Sepal.Length)))

で、こんな感じ

f:id:niszet:20180329080243p:plain

ちゃんとデータが入っていますね。この環境中にデータを保持しているようなので、この生成された関数を他の変数に代入するなどして持っておけば、元になったデータ自体をrm()で削除しても大丈夫そう(一応動いた)です。この環境の値は(iris$Sepal.Lengthのコピーということなのでしょう{^1]。

これで安心してapproxfun()が使えるぞい…!

Enjoy!!