R言語 (4)

外れ値

気象庁から頂いてきた、彼岸あたりの温度変化(3,4月 or 9,10月の日替わり温度セット)を、月の前半・後半に分けて箱ひげ図にするスクリプト(詳細は前回参照)。

library(dplyr)
con <- pipe("sed '1,6d' data.csv | sed 's#/#,#g' | cut -d',' -f1-4,7,10")
so  <- read.csv(con, header=F)
colnames(so) <- c("yy", "mm", "dd", "av", "hi", "lo")

mkgr <- function(k="av") {
    par(mfrow = c(2,5))
    for (Y in unique(so$yy) ){
        q <- so %>% filter(yy== Y) %>% select(k)
        boxplot(q[1:15,], q[16:30,], q[31:45,], q[46:60,],
                main = as.character(Y), ylim = c(-10,30))
    }
}

for (K in c('hi', 'av', 'lo')) {
    pdf( paste(K, '.pdf', sep='') )
      mkgr(k=K)
    dev.off()
}

出来上がったPDFファイルをブラウザーで全て読み込んでおく。そうしておいて、Ctl+TAB で、画面を素早く切り替えれば、パラパラ漫画の出来上がり。わざわざコンビニで印刷して来る事は不要で、楽チンである。

春の彼岸を見ているんだけど、箱ひげ図の上下バーを外れて外側にある値が、小さい丸で表現される、外れ値がやはり有るなあ。並みの言葉で言えば、気温の特異値と解釈出来る。

箱ひげ図(Rが書いてくれるんだけど)の効用として、"データの統計分布での外れ値を発見すること、分布が不均質ではないかを確認することなどのための視覚化表現"って某Webに説明されてた。まさにその通りだな。

Rの箱ひげ図(boxplot)で外れ値を表示させない

箱ひげ図(at Wikipedia)

良いサンプルが出ていた。

set.seed(101)
z <- rnorm(100000)
boxplot(z)
hinges <- qnorm(c(0.25,0.75))
IQR <- diff(qnorm(c(0.25,0.75)))
abline(h=hinges,lty=2,col=4) ## hinges ~ quartiles, blue line
abline(h=hinges+c(-1,1)*1.5*IQR,col=2) #            red line

やってる事が良く分かった(積り)。で、詳細はソース嫁。

> boxplot
function (x, ...)
UseMethod("boxplot")
<bytecode: 0x55d34716e808>
<environment: namespace:graphics>

んが、出てこない。こういう時は、boxplot. に続いてTABを叩き、関係者を教えて貰え。

> boxplot.stats
function (x, coef = 1.5, do.conf = TRUE, do.out = TRUE)
{
    if (coef < 0)
        stop("'coef' must not be negative")
    nna <- !is.na(x)
    n <- sum(nna)
    stats <- stats::fivenum(x, na.rm = TRUE)
    iqr <- diff(stats[c(2, 4)])
    if (coef == 0)
        do.out <- FALSE
    else {
        out <- if (!is.na(iqr)) {
            x < (stats[2L] - coef * iqr) | x > (stats[4L] + coef *
                iqr)
        }
        else !is.finite(x)
        if (any(out[nna], na.rm = TRUE))
            stats[c(1, 5)] <- range(x[!out], na.rm = TRUE)
    }
    conf <- if (do.conf)
        stats[3L] + c(-1.58, 1.58) * iqr/sqrt(n)
    list(stats = stats, n = n, conf = conf, out = if (do.out) x[out &
        nna] else numeric())
}
<bytecode: 0x563d0b91ace0>
<environment: namespace:grDevices>

新 「 数 学 I 」 統 計 分 野 に 於 け る 「 R 言 語 」 支 援 に よ る 教 材 作 成 の 一 例 と し て

学校でもRを使って箱ひげ図を勉強するのか。パソコンと言ったらEXCELって風潮に風穴を開けてもらいたいぞと。

EXCELはユーザーが付きっきりになって操作をせにゃならん。上で挙げたRのスクリプトと同じ事をしようとすれば、切った貼ったを幾千回してやっと、箱ひげ用データが揃う。違うデータを分析しようと思ったら、また同じ事の繰り返し。EXCELは米国防省だか商務省が送り込んだ、日本の生産性を低下させる兵器さ。

まて、EXCELでの操作を録画して、後で繰り返す事だって出来るぞ。他にもマクロを書くって方法だって有るぞ。まあ、好きにしてくださいな。

debugger

ってな訳でRを推すんよ。曲がりなりにもRは言語だ。虫が湧いた時の事を事前に調べておk。ユーザーレベルのデバッグ方法について調べてみた

Rデバッグあれこれ

debug 関数や traceback 関数などを利用

R tips(PDF)

ふむ、エラーになったら取り合えず、 traceback() させればいいんだな。そして虫の潜む関数が判明したら debug(hoge) しろとな。

早速、冒頭のやつでやってみる。最後のforの所を、main()って関数に収容しておいたからね。それとBugを仕込んでおいた。

> main()
Error in xj[i] : only 0's may be mixed with negative subscripts

エラーで止まったのは分かるけど、ちと不親切。

!> traceback()
 6: `[.data.frame`(q, -16:30, ) at ana.R!uHAHRE#5
 5: q[-16:30, ] at ana.R!uHAHRE#5
 4: boxplot.default(q[1:15, ], q[-16:30, ], q[31:45, ], q[46:60,
        ], main = as.character(Y), ylim = c(-10, 30))
 3: boxplot(q[1:15, ], q[-16:30, ], q[31:45, ], q[46:60, ], main = as.character (Y),
        ylim = c(-10, 30)) at ana.R!uHAHRE#5
 2: mkgr(k = K) at ana.R!zb8S0f#4
 1: main()

これで、何かあると何時も不機嫌に文句を垂れてくるpython並みになったな。もう虫の所在は分かってしまったけれど、一応ストーリー仕立てなんで。

 > debug(boxplot.default)
!> main()
 debugging in: boxplot.default(q[1:15, ], q[-16:30, ], q[31:45, ], q[46:60,
     ], main = as.character(Y), ylim = c(-10, 30))
 debug: {
     args <- list(x, ...)
     namedargs <- if (!is.null(attributes(args)$names))
     :
 }
 Browse[2]> c
 Error in xj[i] : only 0's may be mixed with negative subscripts
 >

引数で渡ってきたvecterの範囲がマイナスになってますねぇ(わざとらしいエラー)。まあ、作り付けの関数内にBugなんて事は無いでしょうから、自分で書いたコードを眼を皿にして追う。散歩に出かけてリフレッシュしてくるが、Bug取りの最高の手段だと思うよ。

debug関数は、ターゲットの関数の冒頭にbrowse()を埋め込むんだな。だからbrowse関数のコマンドが使える。n,c,Q ぐらいしか使い道が無いけどね。偽Bugを潰して、boxplotの挙動をステップ実行していける。変数も確認出来るから便利

 Browse[2]> n
 debug: if (is.null(attr(groups, "names"))) attr(groups, "names") <- 1L:n
 Browse[2]>
 debug: names <- attr(groups, "names")
 Browse[2]> n
 debug: cls <- vapply(groups, function(x) class(x)[1L], "")
 Browse[2]> names
 [1] "" "" "" ""
!Browse[2]> x
  [1] 34.1 34.3 32.9 32.2 34.8 34.2 32.9 24.4 30.0 30.3 31.9 28.3 29.5 28.3 25. 9

使い終わったら、undebug(hoge) しておこう。debugonce(hoge)って言う、一度だけdebuggerを呼び出すのも便利だぞ。

範囲

今回初めて気が付いた。いわゆる範囲問題。

> x <- 1:5
> x
[1] 1 2 3 4 5
> x[0:7]
[1]  1  2  3  4  5 NA NA
> x[-1:4]
Error in x[-1:4] : only 0's may be mixed with negative subscripts

タイトルを、範囲なんて事で始めちゃって、範囲にはマイナスを取れないと、一人合点してんだけど、どうやら、超赤恥ものだった。

マイナスは、指定場所の削除を意味するのね。【R言語】行列事始

> x <- 1:5
> x[-3]
[1] 1 2 4 5

答え一発

ちょいと打ちひしがれて、別の道に走る。上の方でEXCELを貶してしまったけど、Rはどうよ? Rを起動して、source(…) して、q() じゃ、まどろっこしい。

答え一発、コマンドラインからの入力だけで仕事を終えたい。これが出来れば、EXCEL連中の鼻をへし折れるってもんです。

sakae@ub:~/ANA$ Rscript ana.R

Attaching package: ‘dplyr’
 :
      yy mm dd   av   hi   lo
609 2019 10 30 13.3 19.0 10.5
610 2019 10 31 12.8 19.4  8.3

スクリプトの中へ tail() を忍ばせて、ちゃんと動いているか確認させた。 パッケージのロードで、何たらかんたら言ってくるのがうざいな。

sakae@ub:~/ANA$ Rscript ana.R >/dev/null 2>&1

これだと、全く静かにさせる事が出来るな。でも、確認用も闇に葬られた。ライブラリーのメッセージだけ消したい。helpしろ。答えは

library(dplyr, warn.conflicts=F)

これで無様なメッセージは抑止出来る。でも臭い物には蓋って態度でいいのか?

元々有った機能をマスクしてまで、ユーザーが指定した関数(ライブラリーのロードだけど)が選ばれているんだから、作者がさぞ気合を入れて書いたものなんでしょう。

debugの有効活用

どんな風に気合が入ってるかソースを眺めてみたい。で、 debug(filter)とかの登場になる。

> source('ana.R')
> debug(filter)
> mkgr()
debugging in: filter(., yy == Y)
debug: {
    UseMethod("filter")
}
Browse[2]> n
debug: UseMethod("filter")
Browse[2]>
debugging in: filter.data.frame(., yy == Y)
debug: {
    as.data.frame(filter(tbl_df(.data), ..., .preserve = .preserve))
}

何か、日が暮れそうな雰囲気だな。

CRAN – search 経由で、 dplyr: A Grammar of Data Manipulation にたどり着く。後はtar玉を落としてきて、、、filter.Rを拝見

filter <- function(.data, ..., .preserve = FALSE) {
  UseMethod("filter")
}

#' @export
filter.data.frame <- function(.data, ..., .preserve = FALSE) {
  loc <- filter_rows(.data, ...)
  dplyr_row_slice(.data, loc, preserve = .preserve)
}

こんな、書式のコメントが有った。helpだかhtmlの原稿になるのかな?

#' @family single table verbs
#' @inheritParams arrange
#' @param ... <[`data-masking`][dplyr_data_masking]> Expressions that return a
#'   logical value, and are defined in terms of the variables in `.data`.
#'   If multiple expressions are included, they are combined with the `&` operator.
#'   Only rows for which all conditions evaluate to `TRUE` are kept.
#' @param .preserve Relevant when the `.data` input is grouped.
#'   If `.preserve = FALSE` (the default), the grouping structure
#'   is recalculated based on the resulting data, otherwise the grouping is kept as is.
#' @return
#' An object of the same type as `.data`. The output has the following properties:
    :

pythonみたいに、関数の中に、 ''' hoge ''' みたいになってなくて、コメント酔い防止と言う意味では(オイラー敵に)好ましいと思うぞ。

つらつら見てたら

> starwars
# A tibble: 87 x 13
   name  height  mass hair_color skin_color eye_color birth_year gender
   <chr>  <int> <dbl> <chr>      <chr>      <chr>          <dbl> <chr>
 1 Luke…    172    77 blond      fair       blue            19   male
 2 C-3PO    167    75 NA         gold       yellow         112   NA
 3 R2-D2     96    32 NA         white, bl… red             33   NA
 4 Dart…    202   136 none       white      yellow          41.9 male
 5 Leia…    150    49 brown      light      brown           19   female
 6 Owen…    178   120 brown, gr… light      blue            52   male
 7 Beru…    165    75 brown      light      blue            47   female
 8 R5-D4     97    32 NA         white, red red             NA   NA
 9 Bigg…    183    84 black      light      brown           24   male
10 Obi-…    182    77 auburn, w… fair       blue-gray       57   male
# … with 77 more rows, and 5 more variables: homeworld <chr>, species <chr>,
#   films <list>, vehicles <list>, starships <list>

こんな楽しいデータを提供してた。

それから、このパッケージはWindowsユーザーに手厚いサポートをしてるように見受けられる。 まあ、EXCEL代わりにR言語を使いましょってのが主たる理由でしょ。そうすると、Rstadioを標準で選ぶだろう。そういうGUIへの親和性は大切ってのは、頷ける所です。

paste0

冒頭の例で、文字列の結合に paste を使った。このpasteには簡易版のpaste0が用意されている。何が簡易かと言うと、helpしろでもいいんだけどソース嫁で行ってみる。

debian(64Bit版)に、R4.0.2を取ってきて入れたんで、それをみる。configure時にX11が無いだとかpcore2が無いだとか抜かすんで、そんなの無いモードにした。make -j4 の威力は強大で、5分少々でコンパイル完了。32BitのシングルCPUでは30分かかったからね。

library/base/paste.R

## change in R 4.0.1: do it this way to allow for calls without new arg
## to work from earlier versions of R.
paste <- function (..., sep = " ", collapse = NULL, recycle0 = FALSE)
{
    if(isTRUE(recycle0))
        .Internal(paste(list(...), sep, collapse, recycle0))
    else
        .Internal(paste(list(...), sep, collapse))
}

paste0 <- function(..., collapse = NULL, recycle0 = FALSE)
{
    if(isTRUE(recycle0))
        .Internal(paste0(list(...), collapse, recycle0))
    else
        .Internal(paste0(list(...), collapse))
}

細かい修正が入っているんですなあ。問題はそこではなくて、C語を呼び出している点。

こんなのを実行

> paste0('hoge', 'fuga')
[1] "hogefuga"

gdb側ではBPを張っておくも、アドレス未解決。当然ヒットせず。

(gdb) info breakpoints
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   <PENDING>  paste0
2       breakpoint     keep y   <PENDING>  _Internal

btすると

#3  0x000055f14bd46584 in Rstd_ReadConsole (prompt=<optimized out>, buf=0x7ffc2\
6de1aac "paste0('hoge', 'fuga')\n", len=4096, addtohistory=<optimized out>) at \
sys-std.c:1039

こんな具合に、入力したのが残っている。はてな?である。

んな訳で、 Rstd_ReadConsole にBPを置いてから違う文字列を評価すると、

> paste0('foo', 'bar')
[1] "foobar"

Rの画面には、先に答えが出ている。

そしてgdbの方は、

Breakpoint 3, Rstd_ReadConsole (prompt=0x55f14bfcb7f0 "> ", buf=0x7ffc26de1aac \
"paste0('foo', 'bar')\n", len=4096, addtohistory=1) at sys-std.c:952
952         if(!R_Interactive) {

どうも、libreadlineが途中で介入してて、かつ割り込みハンドラーも関係してるようで、素直に動かんなあ(一見不思議な現象は、多分readlineが残骸を残しているのだろう)。

Rf_ReplIteration にBPを置いて、地道に追って行くかな。

翌朝、たわわに実った田んぼを散歩してたら、神の声が聞こえた。paste0にBPを貼った時pendingって言われたろう? 何故か考えるのじゃと。

考えたら、mein側にそんな関数はロードされていない、そもそもそんな関数は無いからgdbは対処しようがありませんって事だな。

ならば、C語のエリアでpaste0とかを探してみよう。そして行き着いた所は、paste.c。冒頭にこうある。

/*
  .Internal(paste (args, sep, collapse))
  .Internal(paste0(args, collapse))

 * do_paste uses two passes to paste the arguments (in CAR(args)) together.
 * The first pass calculates the width of the paste buffer,
 * then it is alloc-ed and the second pass stuffs the information in.
 */

最初に引数である文字列の長さを確認。次に長さに見合う場所を確保して、連結するとな。 早速、 do_paste にBPを置いて実行。

(gdb) bt
#0  do_paste (call=0x9f2c48, op=0x928888, args=0x17cf3d8, env=0x17cf458) at paste.c:53
#1  0x005441eb in bcEval (body=body@entry=0x9f0c88, rho=rho@entry=0x17cf458, useCache=useCache@entry=TRUE) at eval.c:6775
#2  0x005502db in Rf_eval (e=0x9f0c88, rho=0x17cf458) at eval.c:616
#3  0x00551e94 in R_execClosure (call=call@entry=0xb4fb6010, newrho=newrho@entry=0x91b9f8, sysparent=<optimized out>, rho=<optimized out>, arglist=<optimized out>, op=<optimized out>) at eval.c:1765
#4  0x00552efe in Rf_applyClosure (call=<optimized out>, op=<optimized out>, arglist=<optimized out>, rho=<optimized out>, suppliedvars=<optimized out>) at eval.c:1693
#5  0x005504a9 in Rf_eval (e=<optimized out>, rho=<optimized out>) at eval.c:739
#6  0x005815ee in Rf_ReplIteration (state=<optimized out>, browselevel=<optimized out>, savestack=<optimized out>, rho=<optimized out>) at main.c:258
#7  Rf_ReplIteration (rho=<optimized out>, savestack=<optimized out>, browselevel=<optimized out>, state=<optimized out>) at main.c:198
#8  0x005819bc in R_ReplConsole (rho=0x93a9e8, savestack=0, browselevel=0) at main.c:308
#9  0x00581a67 in run_Rmainloop () at main.c:1082
#10 0x0048a588 in main (ac=1, av=0xbfeda254) at Rmain.c:29

おお、捕まえたぞ(ライ麦畑で捕まえてじゃなくて、田んぼの神様の思し召し。なんたってここは、お米の国ですから)

    if(use_sep) { /* paste(..., sep, .) */
        sep = CADR(args);
        :
    } else { /* paste0(..., .) */
=>      u_sepw = sepw = 0; sep = R_NilValue;/* -Wall */
        collapse = CADR(args);
    }

これだけ見せられたら、lispのインタープリターだな。安心したぞ。

フレームを少々遡って、evalのコードを見るのが娯楽ですよ。やや、さりげなく JIT なんて語句が見えるぞ。bytecodeを更にマシン語に落とし込んで、加速してるのか。 こういう裏側には、普通のユーザーは関係ないだろうけど。

PDF形式のファイルの用紙サイズを調べる方法は?

PDFファイルを作っているんだけど、用紙サイズってどうなるの? ぐぐったら、アドビの何たらこうたらばかりが出てくる。もうfirefoxで十分でねぇ。しつこく調べたら

PDF形式のファイルをテキストエディタで開き (注:バイナリファイルですので警告画面等が
表示されることがあります) , MediaBox という文字列を探します.この属性はページの
大きさ (印字可能範囲) を示しており,A4縦の場合は通常 [0 0 595 842] が設定されて
います (単位は1/72インチ = 1ポイント).
sakae@ub:~/ANA$ grep MediaBox ZZZZ.pdf
Binary file ZZZZ.pdf matches

本当に調べてくれているのか? grepはファイルの頭の部分を見て、諦めている風。-a オプションを付けて、強引にテキストファイルと解釈させればおk。

sakae@ub:~/ANA$ grep -a MediaBox ZZZZ.pdf
<< /Type /Pages /Kids [ 7 0 R ] /Count 1 /MediaBox [0 0 841 595] >>

これに気が付く前は、

sakae@ub:~/ANA$ strings ZZZZ.pdf | grep Media
<< /Type /Pages /Kids [ 7 0 R ] /Count 1 /MediaBox [0 0 841 595] >>

bainayの海を平気で渡って行く strings なんてのと併用してた。バイナリアンである。

上記は、

pdf('ZZZZ.pdf', paper='a4r' )

として、90度のローテをさせた作成である。


This year's Index

Home