データ穴リストのブログ

すべては喰いっぱぐれないために

cronRで自動化にちゃれんじ

アイがなくてもエーねん*1

そもそも僕がRをちゃんと頑張ろうと決心したのは、世の中のエンジニア氏たちはみんな、 ぱそこん君と対話的だけでなく、パシリのようにこき使えていいな、という思いから始まりました。

やりたいこと、やらないといけないことはいっぱいあるのに、 - そろそろ月末だからアレださなきゃ! - …またこの季節か。。またあの人に頭下げて作業お願いしなきゃ。。 - 前回と同じでいいから、あのファイルはコピーぺして使おう

…とか、そういうの、ストレスでした。 考える時間が欲しい。 考えなくてよい手作業は極力やりたくない。 人に作業振ってもやれ今は他の仕事がだの、やれメンドくさいだの、そういう心のケアばかりにはいい加減付き合ってられない!

ということで、タスクスケジューラを使いたかったのです。

  • cronとは 世のエンジニア氏たちには今更ですね。 非エンジニアの僕と同じような皆さんにはコチラが分かったような分からないような気になってちょうどよろしいかと思います。

Error 403 (Forbidden)|「分かりそう」で「分からない」でも「分かった」気になれるIT用語辞典

で、crontabがどうとかbin/crontabとかetc/crontabとか色々書いてあるけど結局我々はどこに、どう書いたらパシられてくれるのか、それが知りたいのだよ。

そゆこと、前提のないヒト向けの資料はなかなかないですね。

書き方とかは書いてあるんだけどね。

  • cronRとは

そんな痒いところにもうちょっと届きそうで届かない、けどちょっと届くネット社会ですが、 cronRはじゃあなんなのかというと、

これらの2つのパッケージを使用すると、RプロセスをRから直接スケジュールすることができます。これは、基本的なLinux / Unixジョブスケジューリングユーティリティであるcronに直接コマンドを渡すか、Windowsのタスクスケジューラを使用して行います。パッケージはRのスクリプトは、非対話的に実行することができ、できることその事実を知らないRのユーザー始まるために開発された自動化を。

Scheduling R scripts and processes on Windows and Unix/LinuxChromeさんの翻訳のまま引用

相変わらずちょっとカタコトだけどそこは愛嬌ということで。 ちなみに「2つの」といってるのはまぁ書いてありますがWindowsの場合は別なタスクスケジューラというのがあるのでパッケージは別だよ、というだけですね。 今回はそっちはムシです。 もうWindowsでRstudioほとんどいじってないんだもの。

cronの設定、しなきゃしなきゃ…とは思ってたんだけど、腰が重くてなかなかたどり着かず状態だったので、Rstudio内でこの操作を完結できるのはとてつもなくありがたかったですね。 無論、ちゃんとしたLinuxユーザにはどうでもいいパッケージだというのはよく分かるけど。

あと、このパッケージはRstudioAddinもあって、今回初めてRstudioAddinを使ってみました。

  • 導入

スクショとか入れると親切でしょうけど面倒なのでいれません。

  • RstudioのAddinボタンをポチるのもいいけど、install.package("Addinslist")がステキ
  • Addinlistでアドイン一覧を眺める。
  • cronRをインストール

ちなみにAddinslistの紹介はこちら www.karada-good.net

…さあ!これで使えるのかな?と思ったら「ShinyFilesがないよ」とエラーが出る。 パッケージを入れようにもできない。

なんだこりゃ?と思っていたら、先の開発者さんのgithubを見たら、 どうやらアドインを使うには更に別のパッケージも必要だとか。 ということで下記を実行

install.packages('miniUI')
install.packages('shiny')
install.packages('shinyFiles')

これで準備が出来ました。

結論からいうと、Rスクリプトファイルが消えました。

  • 自動実行するスクリプトを選択して、
  • ジョブの名前を書いて、
  • ジョブのタイミングを設定して、
  • リポジトリだかってとこのパスを指定して

ポチッ!

としたら、同名の空のファイルの出来上がり。

ふぁ!?!?!?!?!?!?!?!?!?!?!?

どうしようどうしよう…

そういえばバックアップとか全然とってない…

git管理もしようと思ってたけど全然理解してない…

指定したスクリプトはかなり前に作って定期的にポチポチ実行していた 自分の中では割と重宝しているものだったので、頭真っ白。

ああ、イチからコード書き直さなきゃなのか…金曜の夜に…もうムリ… と思ったら、Dropboxさんが復元してくれた。…ギリギリセーフ。。。

なにがいけなかったって、 リポジトリのパスを設定する際に、元ファイルと同じ場所に指定なんかしたもんだから*2、 まっさらなファイルを上書き保存してくれたってだけだった。。。

というわけで、今一度上記の引用先*3の動画を見て、 リポジトリ用のディレクトリを作って、ようやく自動化できた。

あの時の衝撃をぼくはわすれない。

バックアップもそうだけど、まずテストスクリプトで試せよってね。

とにもかくにも、これでパソコン君をパシらせることが出来るようになりました。

さぁどんどんパシらせよう。

*1:AI、つまりインテリジェンスがなくたってA、AIだと人工的ってなるけどここではAutomationだったらいいんだよ、というユーモアです。それを説明するというね。

*2:結果のlogファイルが出来る場所くらいに思ってたので…

*3:http://www.bnosac.be/index.php/blog/64-scheduling-r-scripts-and-processes-on-windows-and-unix-linux

lubridateで年齢計算(Excelのアレ)

lubridateーーそれは愛しい、時間概念とのアバンチュール

今宵のステキな刻(とき)を彩る、珠玉のLovely date.....


…と、外で書いててかなり恥ずかしい。

■今回はlubridateパッケージについてです。

lubridate、とても便利!という声は聞きつつも、あんまり他のブログに書かれてないんだもの。

他の記事といえば以下のあたりを参考にさせていただきました。
estrellita.hatenablog.com
www.karada-good.net

この辺りのブログはもうアレですね。
私にとっての貴重な情報源です。

もうホント、毎日のように仕事もしないで参考にさせていただいてます!

■そうなんだけど

だけど、そこから先の、日付データの、次の処理がない。

会員情報とか扱ってるようなお仕事をしてる方だったら、生年月日とか持ってますよね?

年齢とか計算したくないですか……!?

ましてや、学生等々、その1学年だったり、世代の観点が重要な分析テーマとか、
需要ありますよね!?!?


僕は欲しかったんです。
そういう処理方法のヒント。
無かったから試行錯誤しながら泣いてたんです。

全国のRおじさんの皆様、そういうことなんです!

■R以外でやればいいじゃん

…そりゃね、他の言語でなさる方はそうでしょうね。

あるいは我らが神、excel様でも便利な関数ありますよね。

そう、datedif()


えぇ、よくお世話になりました。


なぜあんな便利な関数が隠しコマンド的な扱いになってるのか、未だにナゾです。


でもね

数百万レコードを一気に計算したいっつったらExcel無理じゃんね

感覚的には、Excelは5万行超えたらもう無理。
計算とかしてフリーズしたりイライラしたくない。

他言語?できるだけ1つのツール内で完結したいですぅ

エンジニア氏に任せればいい?
時間がかかるならその部分未処理でいいからデータ早よクレ。


という訳でやっちまいましょう。

…と思ったんですが、ビギナーの僕にはやや荷が重かった。

■そこで天使のおわすサンクチュアリ、r-wakalangへ

いつも悩める子羊である僕の低レベルな質問にもすんなり答えてくださる先人たち、本当にありがとうございます!

みんなも困ったらココへ!!
qiita.com


で、何か時間間隔を計算できないかなーと思っていたら、日本R界の天使*1が広大なRの海の中から探し出してくださいました、

time_length()

この関数は、2つの日付データの間隔を割り出して返してくれるもので、unitに単位を指定すればその単位の間隔を出力してくれるすぐれもの!

…あれ?これどっかで見たことあるぞ?

そう、まんまこれ、Excel様のdatedifなんですね。

■細かな挙動は少々異なる

とはいえまぁ、単なる表のExcel野郎 Excel様と違って、
Rだし、ちゃんとベクトル単位で計算可能です。

x <- "2000-04-02"
y <- "2016-04-01"

x2 <- c("2000-03-31","2000-04-01","2000-04-02","2000-04-05","2000-04-06","NA","2016-04-01","2016-04-02","1916-04-01","1916-04-02")

dif <- as.Date(y) - as.Date(x) 

dif2 <- as.Date(y) - as.Date(x2) 

time_length(dif,"year") # unitで指定した年単位の値が返ってくる

time_length(dif2,"year") # ベクトルの長さが違っても出来る

■では、年齢計算してみます

先に以下のサイトを参考にしてたのもあって、関数に初挑戦してみました。
blog.goo.ne.jp

calc_age <- function(birthday, obsdate){
  dif <- as.Date(obsdate) - as.Date(birthday)
  trunc(lubridate::time_length(dif,"year"))
}

# あれ…?

calc_age(x, y)

calc_age(x2, y)


よしよし、出来た。と思いきや、どうも日付が微妙にズレてる。。

原因は、データ型の問題でした。

元々あるdifftime型だとうるう年を考慮してくれないようなので、lubridateにあるinterval型にします。

# こっちだとNG
class(dif <- as.Date(y) - as.Date(x))

# interval型にする
class(dif <- interval(as.Date(y)), as.Date(x)))


calc_age <- function(birthday, obsdate){
  dif <- interval(as.Date(birthday), as.Date(obsdate))
  trunc(lubridate::time_length(dif,"year"))
}

できたできた。
仕事柄、「学年」でまとめたい時には4/1時年齢とかで計算したいから、観測日年齢のズレは困っちゃうので、interval型大変便利です*2

ついでに上限・下限も決めちゃっておかしな元データは計算しないことにする。

calc_age_limits <- function(birthday, obsdate, lower = 0, upper = 100){
     dif <- as.Date(obsdate) - as.Date(birthday)
     check <- trunc(lubridate::time_length(dif,"year"))
     check1 <- replace(check,which(check>=upper),values = NA)
     replace(check1,which(check1<lower),values = NA)
}

calc_age_limits(x2, y) #デフォルトでは0歳未満、100歳以上をエラーとする、ということにしてます

■欠損値が2回続くとダメ?

と、ココまで来て満足していたら、いざ実データでやってみるとエラーが出て動かない。。

なんでか全然分からずに、色々テストしてみたら、どうもNAが2個以上ベクトル内にあるとダメっぽい。

x3 <- c("2000-03-31","2000-04-01","2000-04-02","2000-04-05","2000-04-06","NA","2016-04-01","2016-04-02","1916-04-01","1916-04-02", "NA")

calc_age_limits(x3, y) # エラーになる


なんぞこれ…バグじゃないんすか。。。
なんか解法があるんすか。。。

タスケテ!R-wakalang!


ということでまた相談したら、またも天使が確認してくださるしバグ報告してくださるしとりあえずの対処法を示してくださるという。。。
まさにAngel of Angels!*3

■修正すると*4

こんな感じです

calc_age_limits2 <- function(birthday, obsdate, lower = 0, upper = 100){
    diff <- interval(as.Date(birthday), as.Date(obsdate))
    na_idx <- is.na(diff)
    result <- vector("numeric", length(na_idx))
    result[na_idx] <- NA
    result[!na_idx] <- lubridate::time_length(diff[!na_idx],"year")
    result2 <- replace(result,which(result>=upper),values = NA)
    result2 <- replace(result2,which(result2<lower),values = NA)
  trunc(result2)
}

# result2って…ダメな書き方ですよね。。。すみません。。

calc_age_limits2(x3, y)


ということで、NAがあっても大丈夫な関数になりました。*5

別にtime_length()のままでも使用には支障はないんだけど、整数で結果が出ないしinterval型にしないと正しく返らないので、
「コレ叩いたらすぐできるよ」ってドヤ顔する為に関数の方がいいかなということで、関数をご紹介しておきます。

これを応用すると(応用でもないけど)、
まんまExcelのdatedif()がRでできます。

datedif <- function(startdate, enddate, Units = "year", lower = NA, upper = NA){
    diff <- interval(as.Date(startdate), as.Date(enddate))
    na_idx <- is.na(diff)
    result <- vector("numeric", length(na_idx))
    result[na_idx] <- NA
    result[!na_idx] <- lubridate::time_length(diff[!na_idx], unit = Units)
    result2 <- replace(result,which(result>=upper),values = NA)
    result2 <- replace(result2,which(result2<lower),values = NA)
  trunc(result2)
}


# テスト (1行目はcalc_age_limitsと同じになります)
datedif(startdate = x2, enddate = y, Units = "year", lower = 0, upper = 100)
datedif(startdate = x2, enddate = y, Units = "year")
datedif(startdate = x2, enddate = y, Units = "month")
datedif(startdate = x2, enddate = y, Units = "day")
datedif(startdate = x2, enddate = y, Units = "hour")
datedif(startdate = x2, enddate = y, Units = "minute")
datedif(startdate = x2, enddate = y, Units = "second")


こちらはUnitsを変えられるようにしてあるので*6、upperとlowerはそれぞれデフォルトはNAにしときました。

※小数点以下を知りたいときはtrunc()を消してください。まぁそうすると関数の意味殆ど無い気もするけど。

ということで、大量データでもこうした対処で簡単に時間感覚が計算出来るようになりました。



これで

  • 対エンジニア氏:え?計算に時間がかかる?したら生データでいいから早めにデータください(ドヤァ

というコトが可能になるわけです。

ライフハックですねぇ。



今回は以上です*7

*1:@yutannihilation氏にはいつも大変お世話になっております…!というか今回の内容、ほとんどが@yutannihilation氏に教えていただいたものを記事化しただけです。。。

*2: ズレて良いってことはまぁ大概ないですね

*3:聖yutani氏を讃えよ!

*4:実は、天使が調べてくださった情報では、lubridateのバージョンを1.6.0にあげたら解決するということがgithubにも書いてあったのですが、バージョンあげてもなぜか出来なかったので、以降は天使が授けてくれた対処法を元にしています

*5:ちなみにNAの連続でエラーになるのはこのissueらしいですが、ちょっとコレわからない…github.com

*6:って元々time_lengthの仕様ですけどね

*7:とにかく言いたいのは、R-wakalangありがとうということと、中でもいつも回答くださるyutani氏に感謝、ということです