Spark DataFrameに新しい列を追加する

下記のようにUDF使うか, mapでDataFrameごと新しくするか、なのか?

import spark.implicits._
import org.apache.spark.sql.functions._

// Example data
val df = Seq(
    (1, 2),
    (3, 4),
    (5, 6)
).toDF("x", "y")

// Define function
val hoge = udf({(x: Int, y: Int) =>
    x + y
})

val x = df.withColumn("z", hoge($"x", $"y"))
x.show()

stackoverflow.com

sum_{i=1}^{N}(標準正規分布×標準正規分布) = 標準正規分布×自由度Nのカイ分布

なんでこうなるかの数式の証明はまだ読み切れてないんだが、とりあえずコード書いて検算したのとLINKのメモ。確かにヒストグラムがほぼほぼ重なる…

#自由度とサンプルサイズ
df <- 10
size <- 10^4

# 標準正規分布×自由度dfのカイ分布(カイ二乗分布に従う乱数の平方根)
x <- rnorm(size)*sqrt(rchisq(size, df))
# sum(標準正規分布×標準正規分布)、df個だけ足す
y <- purrr::map_dbl(seq_len(size), ~ sum(rnorm(df)*rnorm(df)))

#ヒストグラムが重なるかのチェック
ggplot2::ggplot(data.frame(x=c(x, y), type=rep(c("x", "y"), each=size)), ggplot2::aes(x = x, fill = type)) + 
  ggplot2::geom_histogram(position = "identity", bins=sqrt(size), alpha = 0.5) 
  

f:id:teramonagi:20181108223022p:plain

後で調べる

rlangと戯れて気が付いたこやつとの私的正しい付き合い方

掲題の件、Tidy Evalというかrlangパッケージを、下記の記事をはじめいろいろ使いこなそうと試行錯誤してきた。 いい加減、俺なりの楽なやり方が見つかったのでまとめておく。

eval(parse(text=...))をモダンに書きたい - My Life as a Mock Quant

全ての引数の名前と値をlistとして取得したい - My Life as a Mock Quant

rlangパッケージとかTidy Eval周りをお勉強するための日本語の参考資料は、油谷さんのこれしかほぼこれしかないと思う。

dplyr再入門(Tidyeval編) - Speaker Deck

ちなみに俺はとっかかりとして、このHadleyの動画が一番しっくりきた。 www.youtube.com これを見た後

Programming with dplyr • dplyr

を読めばなんとなく使いこなせるようにはなる(気がする)。

で、結局どうするのがいいの?

結論としては「俺は(いつもの)dplyr的な書き方を捨てるぞ~Hadley~」である。 なんでそんなことになるのかというと、普通にdplyr使ってる分には当然楽で超いいんだが、それを関数化したい、似たような処理を何回もやりたい、そんなときが俺には多い。 従って、その状況をどうにかするSomethingを考えた。

たとえばこんなデータフレームがあったとしよう。

> set.seed(71)
> df <- data.frame(
+   x = 1:12,
+   key1=sample(letters[1:4], 12, replace=TRUE),
+   key2=sample(letters[1:4], 12, replace=TRUE),
+   key3=sample(letters[1:4], 12, replace=TRUE)
+ )
> df
    x key1 key2 key3
1   1    b    a    a
2   2    c    d    b
3   3    b    d    b
4   4    a    d    b
5   5    b    a    b
6   6    d    d    d
7   7    c    d    a
8   8    d    d    c
9   9    b    c    d
10 10    b    b    a
11 11    a    c    d
12 12    d    a    d

このdata.framekey1, key2, key3のそれぞれでグループ化して件数をカウントしたい、そんな状況を考える。 dplyrに習熟している賢明な読者諸君はこんな風に書くだろう。

> library("dplyr")
> dplyr::group_by(df, key1) %>% dplyr::summarize(count=n())
# A tibble: 4 x 2
  key1  count
  <fct> <int>
1 a         2
2 b         5
3 c         2
4 d         3
> dplyr::group_by(df, key2) %>% dplyr::summarize(count=n())
# A tibble: 4 x 2
  key2  count
  <fct> <int>
1 a         3
2 b         1
3 c         2
4 d         6
> dplyr::group_by(df, key3) %>% dplyr::summarize(count=n())
# A tibble: 4 x 2
  key3  count
  <fct> <int>
1 a         3
2 b         4
3 c         1
4 d         4

「あぁ、dplyrは最高だ、楽だ」とお思いだろう?わかる、俺にもその気持ちがわかる。 わかるが、俺には似たようなCodeを三回も手で書くのは宗教上禁止されているので、こいつを関数化してしまいたいのである。

そして俺は決意した、所謂「dplyrでスタンダードな書き方(非標準評価、Non Standard Evaluation, NSE)」を捨てる、と。 そのために以下のような関数を書いた。

> f <- function(df, key){
+   if(!rlang::is_quosure(key)){
+     key <- rlang::sym(key)
+   }
+   dplyr::group_by(df, !!key) %>% dplyr::summarize(count = n())
+ }

こいつを使うとpurrrパッケージと組み合わせて(別に組み合わせないでsapplyとか使えばいいんだけど)、以下のように書ける。 まさに一撃ワンライナーだ。気持ちがいい。

> purrr::map(1:3, ~ f(df, paste0("key", .x)))
[[1]]
# A tibble: 4 x 2
  key1  count
  <fct> <int>
1 a         2
2 b         5
3 c         2
4 d         3

[[2]]
# A tibble: 4 x 2
  key2  count
  <fct> <int>
1 a         3
2 b         1
3 c         2
4 d         6

[[3]]
# A tibble: 4 x 2
  key3  count
  <fct> <int>
1 a         3
2 b         4
3 c         1
4 d         4

そして先ほど宣言したように、俺は非標準評価、要するに列名を直渡しするスタイルを捨てたのでその場合にはエラーになる。

> f(key1)
Error in rlang::is_quosure(key) : 
  argument "key" is missing, with no default

この状況でも一応、動かすことはできて、その場合にはrlang::quo()を用いればよい。 一件落着である。

> f(df, rlang::quo(key1))
# A tibble: 4 x 2
  key1  count
  <fct> <int>
1 a         2
2 b         5
3 c         2
4 d         3

「なんでこの関数fでうまくいくか?」についてはがんばってrlangを勉強して学んでもらいたい私もまだ道半ばなので解説はなしだ。

”関数の引数になってる変数名を取得する”を新旧の書き方で書く

前も書いた気がするんだが一応。 rlangのほうがモダンなやつ

> f1 <- function(x){rlang::expr_text(rlang::enexpr(x))}
> f2 <- function(x){deparse(substitute(x))}
> a <- 123
> f1(a)
[1] "a"
> f2(a)
[1] "a"

パッケージ開発中に依存パッケージインストールするにはdevtools::install_deps()

とりあえず開発してるPackageをRStudioあたりで開いておいて、以下を叩けばOKだって。

devtools::install_deps(dependencies = TRUE)

なるほどね~ - https://www.rdocumentation.org/packages/devtools/versions/1.13.6/topics/install_deps

DataExplorer packageでデータを大雑把に把握する

「データを大雑把にとらえるために何も考えずにこれに突っ込む」みたいな感じで使えてよい。
あと、BLOG移転したい。

(パッケージは別途インストール済として)たった↓だけのコードで

library("DataExplorer")
create_report(iris)


こういう結果がでる。

report.html - Google ドライブ


楽じゃない?
詳しくはVignettesを見てもらいたい。

Introduction to DataExplorer

Enjoy!

あれば環境変数からとる、なければGlobal環境からとる

掲題の件がやりたいので、こんな関数をかいた。

get_from_env_or_global_env <- function(x){
  if(Sys.getenv(x) != ""){
    Sys.getenv(x)
  } else if(exists(x)){
    eval(parse(text = x), envir=.GlobalEnv)
  }
}
#環境変数からとる
> get_from_env_or_global_env("OS")
[1] "Windows_NT"
#Global環境からとる
> a <- 111
> get_from_env_or_global_env("a")
[1] 111

この手のUtilityをPackageにまとめたい気もする。