株式会社ホクソエムのブログ

R, Python, データ分析, 機械学習

モデルで扱うデータの前処理をrecipesで行う

ドーモ。ホクソエムの @u_ribo です。本業ではモデリングとは離れたギョームをしています。寂しくなったので、Rのrecipesパッケージについて紹介します。

tidymodels.github.io

モデルに適用するデータの前処理

Rでのモデル式 (model formula) の記述って、利用時に不便を感じることや覚えるのが難しい面が時々ありませんか?例えば、y ~ .」は右辺のドットが、目的変数以外の全ての変数を説明変数として扱うことを示しますが、説明変数に対数変換などの変数変換を行うにはy ~ log(.)という記述はできず、結局、説明変数を「+」でつなげていくことになります。また、交互作用項の指定には「x1 * x2」や「(x1 + x2)^2」、「:」を使う表記が可能ですが、この表記には最初は混乱しませんか?(単に私が不勉強なだけということもあります)

加えて、多くのモデルでは欠損への処理が必要となったり、適用するモデルによって(例えば複数の説明変数を扱うK-NNやSVM)は、変数間の標準化が必要です。

ここで紹介するrecipesパッケージを使うことで、こうしたモデル構築に伴うデータの前処理を楽にすることが期待されます。

  • モデルに適用するデータの前処理
  • 対数変換を行うモデルを例に
    • recipesパッケージによるデータの前処理
  • 交互作用を扱うモデルを例に
  • stepに指定可能な処理
  • まとめ
続きを読む

クロネッカー積でデータを列方向(or行方向)に高速に複製もしくは定数倍する

r-wakalangからの転載です。以下のような質問がありました。

data.frameをカラム・ロウ方向に複製結合したdata.frameを出力させたいのですが、どうも綺麗に書けずです。。アドバイスお願いしますm( )m

この意味は、例えば「行方向に2個・列方向に3個複製」の場合は以下の図です。

f:id:StatModeling:20180824174855p:plain

データフレームが数値のみの場合、これは実はクロネッカー積と呼ばれる行列の特殊な積で表現できます。

  • Rで書く場合

Rではデフォルトで%x%関数(or kronecker関数)が用意されているのでそれを使えば簡単に実装できます。

B <- as.matrix(data.frame(a=1:3, b=4:6))
A <- matrix(1, nrow=2, ncol=3)
res <- A %x% B
  • Pythonで書く場合

Pythonでもnumpykronメソッドが用意されていますので、以下のように書くことができます。

import pandas as pd
import numpy as np

B = pd.DataFrame(np.arange(1,7).reshape(2,3).T, columns=list('ab'))
A = pd.DataFrame(np.ones((2,3)))

kp = pd.DataFrame(np.kron(A,B), columns=pd.MultiIndex.from_product([A,B]))

  * * *  

定数倍も行けます。例えば、以下のように

f:id:StatModeling:20180824163606p:plain

左のデータフレームから右のデータフレームが得たい場合です。

  • Rで書く場合
A <- as.matrix(data.frame(a=1:3, b=4:6))
B <- matrix(1:50, nrow=1)
res <- A %x% B
  • Pythonで書く場合
import pandas as pd
import numpy as np

A = pd.DataFrame(np.arange(1,7).reshape(2,3).T, columns=list('ab'))
B = pd.DataFrame(np.arange(1,51).reshape(1,50))

kp = pd.DataFrame(np.kron(A,B), columns=pd.MultiIndex.from_product([A,B]))

Enjoy!

ggplot2 で時系列の区間に影をつけるのは annotate が便利ぽい

例えば次のような時系列データがあるとします。

library(xts)
ts <- as.xts(Nile)

library(ggplot2)
autoplot(ts)

f:id:hoxo_m:20180821233320p:plain

このプロットの 1900年から1940年までの区間に影をつけたい。

これには annotate() が便利ぽい。

autoplot(ts) + 
  annotate("rect", 
           xmin = as.Date("1900-01-01"), 
           xmax = as.Date("1940-01-01"), 
           ymin = -Inf, ymax = Inf, alpha = 0.2)

f:id:hoxo_m:20180821233547p:plain

Enjoy!

参考

RStudioServer から ShinyApp を直接デプロイしたい

現在 CentOS 7.5 サーバーに RStudioServer と ShinyServer を入れて RStuidio 上で ShinyApp を書いています。

デプロイするには /srv/shiny-server/ の下にフォルダを丸ごとコピーしていますが、サーバにいちいちログインするか RStudio の新機能である Terminal 上でコピーしちゃうんですが、これを R でやりたいというのが今日のお題です。

適当に書くと次のようになると思います。

deploy_shiny_app <- function() {
  # 現在のワーキングディレクトリに ShinyApp があるとする
  dir_path <- getwd()
  # ShinyApp のデフォルトのデプロイ場所は /srv/shiny-server/
  target_dir_path <- file.path("/srv/shiny-server", basename(dir_path))
  # ShinyApp のフォルダにある .R ファイルを全て取得
  files <- list.files(dir_path, pattern = "\.R$", full.names = TRUE)
  # sudo cp コマンドで全てコピー
  command <- sprintf("sudo cp -vfu %s %s", paste(files, collapse = " "), 
                     target_dir_path)
  system(command, input = rstudioapi::askForPassword("Enter password"))
}

rstudioapi::askForPassword() というのはコード上にパスワードを平打ちしたくない時にこれ書いとくと RStudio 上でパスワードを入力するプロンプト出してくれるナウいやつです。

さて、これを実行すると次のようなエラーが出ました。

sudo: no tty present and no askpass program specified

これはなんかセキュリティ的な制限で、デフォルトでは sudo 時のパスワードが表示されないようにしてるみたいです。 解除するには

$ sudo visudo

で sudoers ファイルを開いて

Defaults visiblepw

と書くと良いらしいです。

さて、これで実行するとうまくコマンドが実行されるわけですが、デプロイ先のフォルダがないとエラーが出てコピーできません。 なので、フォルダがなければ作るということをやります。

sudo_create_dir_if_not_exists <- function(dir_path) {
  if (!dir.exists(dir_path)) {
    command <- sprintf("sudo mkdir %s", dir_path)
    prompt <- sprintf("Create %s", basename(dir_path))
    system(command, input = rstudioapi::askForPassword(prompt))
  }
}

deploy_shiny_app <- function() {
  # 現在のワーキングディレクトリに ShinyApp があるとする
  dir_path <- getwd()
  # ShinyApp のデフォルトのデプロイ場所は /srv/shiny-server/
  target_dir_path <- file.path("/srv/shiny-server", basename(dir_path))
  # なければ作る
  sudo_create_dir_if_not_exists(target_dir_path)
  # ShinyApp のフォルダにある .R ファイルを全て取得
  files <- list.files(dir_path, pattern = "\.R$", full.names = TRUE)
  # sudo cp コマンドで全てコピー
  command <- sprintf("sudo cp -vfu %s %s", paste(files, collapse = " "),
                     target_dir_path)
  system(command, input = rstudioapi::askForPassword("Enter password"))
}

これでうまくいきそうなもんですが、ShinyApp の静的ファイルを www に入れたりモジュールをサブディレクトリに保存していたりという場合が考えられます。 なので指定したサブディレクトリもコピーするという記述も付け加えます。 このとき、フォルダからフォルダへコピーするという動作は共通なので関数化しちゃいます。

copy_files <- function(dir_path, target_dir_path) {
  # なければ作る
  sudo_create_dir_if_not_exists(target_dir_path)
  # ShinyApp のフォルダにある .R ファイルを全て取得
  files <- list.files(dir_path, pattern = "\\.R$", full.names = TRUE)
  # sudo cp コマンドで全てコピー
  command <- sprintf("sudo cp -vfu %s %s", paste(files, collapse = " "),
                     target_dir_path)
  system(command, input = rstudioapi::askForPassword("Enter password"))
}

deploy_shiny_app <- function(dir_path = getwd(), dest = "/srv/shiny-server") {
  # ShinyApp のデプロイ先
  target_dir_path <- file.path(dest, basename(dir_path))
  # コピーの実行
  copy_files(dir_path, target_dir_path)
}

コピーすべき subdir も指定可能にして for 文で回します。

deploy_shiny_app <- function(dir_path = getwd(), dest = "/srv/shiny-server", 
                             subdir = c("www")) {
  # ShinyApp のデプロイ先
  target_dir_path <- file.path(dest, basename(dir_path))
  # コピーの実行
  copy_files(dir_path, target_dir_path)
  
  # サブディレクトリのコピー
  for (subdir_name in subdir) {
    subdir_path <- file.path(dir_path, subdir_name)
    if (dir.exists(subdir_path)) {
      target_subdir_path <- file.path(target_dir_path, subdir_name)
      copy_files(subdir_path, target_subdir_path)
    }
  }
}

これで多分うまくいきます。 最初デプロイ先のフォルダがない場合はフォルダの作成、サブフォルダの作成とパスワードを何度も聞かれてうざいですが、まあしょうがないかなと。

もっと良い方法があれば教えてください。

Enjoy!

雑記

こんにちは、ホクソエムです。雑記です。

今では当たり前のように使われている pipe演算子こと %>% 。

dplyrパッケージが発表された当初は「気持ち悪い」と評判だったのですが、みなさんもう慣れたのでしょうか。

そしてrlistパッケージの %>>% 。

%>% よりハヤイ!!!!と当時は盛り上がっていましたが誰か使っている方はいるのでしょうか(私は使っていない)。 私が知らないだけで%>>>%とか%>>>>%とかあるのかもしれません。 「私はこんなpipe類似演算子を使っているよ!!!」というご報告お待ちしております。

最後に、pipeを使いたくない方にpipeを使ったコードを従来の表記に戻してくれるパッケージも開発されていましたが、皆さんこちらはご存知でしょうか。

https://github.com/TobCap/demagrittr

どんなものにも歴史はあるもので、流行っているその時にしか味わえない現在性を堪能することも、技術を追いかける醍醐味です。 言語開発の片隅に咲いたそんな一輪の花たちをこれからもお伝えしていきたいと思います。

Rユーザ会でStanの紹介と応用事例について話しました

Rユーザ会@統計数理研究所で「StanとRでベイズ統計モデリング」について発表しました。資料は以下です。

Rには詳しいがStanをほとんど知らない人たちへのStan紹介と、(空間)統計モデリングに詳しい人たちへのちょっと凝った応用事例の二部構成にしたため、易しい部分とかなり難しい部分のどっちつかずの内容になりました。ありがち。藤野さんをはじめとする皆様、ありがとうございました!

最後の参考文献のところにあるリンク先は以下になります。

Enjoy!

awe.s3パッケージでRからのAWS S3とのファイルやりとりを行う

ドーモ。ホクソエムです。更新が久しくなってしまいました。ホクソエムでは現在、Amazon Web Service (AWS)を利用していないのですが、本職の方でS3に触れる機会があったので、RからS3への操作を行うためのパッケージ awe.s3 を紹介したいと思います。

ASW S3とは、AWSが提供するサービスの一つで、オンラインでのファイルストレージとして利用できます。ストレージするファイルの容量・種類は問わないので、一時的なデータや画像の保存先として使われているのではないでしょうか。また、柔軟にアクセス制限やファイルのライフサイクル(自動的な削除)がかけられるのも特徴です。S3では、バケットと呼ばれるフォルダのような構造と、オブジェクト(データ)を管理します。

awe.s3パッケージは、数多くの便利なRパッケージを行っているROpenSciのメンバーでもあるThomas J. Leeperらが活動するcloudyrというRのチームが開発しています。cloudyrのリポジトリには、今回紹介するawe.s3のほか、同じAWSのサービスであるES2管理やLambdaのためのaws.ec2aws.lambdaなども含まれています。

github.com

awe.s3はCRANに登録されていますので、次のコマンドでインストールしましょう。また、S3の操作に必要なアクセスキーとIDは、IAM (Identity and Access Management) Management Consoleから発行しておいてください。

install.packages("aws.s3", dependencies = TRUE)

library(aws.s3)

アカウントとの紐付け

早速、ストレージしたファイルへのアクセスを行いたいところですが、まずはアクセスキーIDとシークレットキーを使った認証を行うことが必要です。

cloudyrが携わるAWS関係のRパッケージでは、Sys.setenv()で設定されている環境変数を利用します。これらが.Rprofile等に記載されていない場合は、コンソールでSys.setenv()を行いましょう。必要な情報は、アクセスキーID、シークレットキー、利用しているリージョン(地域)です。

# 環境変数の値を確認
Sys.getenv("AWS_DEFAULT_REGION")
# [1] ""

Sys.setenv("AWS_DEFAULT_REGION" = "<リージョン>", # us-east-2 など
           "AWS_ACCESS_KEY_ID" = "<アクセスキーID>",
           "AWS_SECRET_ACCESS_KEY" = "<シークレットキー>")

複数アカウントがある場合、AWSの発行するcredentialsファイルを使った署名を行うこともできます。これにはawe.s3インストール時に依存パッケージとしてインストールされるaws.signatureuse_credentials()を使います。

# defaultのアカウント情報を用いた署名
aws.signature::use_credentials()

# hoxouri ユーザのアカウント情報を利用する場合
aws.signature::use_credentials(profile = "hoxouri")

接続が成功しているかを確かめるため、バケットの一覧を表示してみます。

bucketlist()
#              Bucket             CreationDate
# 1 aws.s3.test170418 2017-04-18T04:35:22.000Z
# 2        hoxom-blog 2017-07-18T11:02:47.000Z

うまくできているようですね。

特定のバケットのオブジェクトを出力するにはget_bucket()を使います。

get_bucket("aws.s3.test170418")
# Bucket: aws.s3.test170418 
# 
# named list()

どうやらこのバケットにはまだ何も入っていないようです。

オブジェクト操作

それでは、バケットに対してオブジェクト(データ)を保存したり、バケット内のオブジェクトへの操作を行いましょう。awe.s3では、次のようなオブジェクト操作が可能です。

  • Rオブジェクト(.Rdata, .rds)の読み書き (s3save(), s3saveRDS())
  • R関数を使ったRへの読み書き (s3read_using(), s3write_using())
  • ローカルファイルのバケットへの保存 (put_object())
  • バケットからのローカルへの保存 (get_object())
  • バケット、オブジェクトの削除 (delete_bucket(), delete_object())

例として、mtcarsオブジェクト(データフレーム)をS3に保存します。Rオブジェクトとして保存したい時はs3save()で行います。

s3save(mtcars, bucket = "aws.s3.test170418", object = "mtcars.rds")

第一引数で対象のRオブジェクト、第二引数で対象のバケット名、第三引数のobject引数ではオブジェクト名を与えます。関数の実行後、コンソールには何も表示されませんが、エラーがでなければアップロードは成功しているはずです。改めてバケットの中身を出力してみましょう。

get_bucket("aws.s3.test170418")
# Bucket: aws.s3.test170418 
# 
# $Contents
# Key:            mtcars.rds 
# LastModified:   2017-07-18T11:33:04.000Z 
# ETag:           "1bf2269b855ca97b628582dc29962eb1" 
# Size (B):       1235 
# Owner:          suika1127 
# Storage class:  STANDARD

次はcsvをアップロードする例です。Rオブジェクトではなくcsvなどのファイルで保存したい時はreadr::write_csv()などの関数を使いテキストファイルにしておきましょう。またその際はput_object()を使い、ファイルのアップロードを行います。

mtcars %>% 
  readr::write_csv("sample_mtcars.csv")
put_object(file = "sample_mtcars.csv", 
           object = "sample_mtcars.csv", 
           # バケットにはフォルダを作ることができますが、bucket引数で指定(なければ作成される)できます
           bucket = "aws.s3.test170418/csv")

今度は保存したオブジェクトをRで利用可能な状態にします。対象がRオブジェクトであればs3loadreadrreadxlで読み込めるファイルであればs3read_using()を用います。

s3load("mtcars.rds", bucket = "aws.s3.test170418")
ls()
# [1] "mtcars"

s3read_using(readr::read_csv, object = "sample.csv", 
             bucket = "aws.s3.test170418/csv")

rdsファイルに保存したmtcarsオブジェクトが利用できるようになりました。

私も使い始めたばかりで、aws.s3パッケージの全てを紹介しきれませんが、基本的なことはできたかと思います。つどコンソールを叩かず、集計結果等を保存できる、データを引っ張ってこれるので便利ですね。

Enjoy!