R on OpenBSD

install R

どういう訳かこの所ずっとリナ系にべったり。OpenBSDはさっぱりかまって貰えないで泣いている。ならば、Rの一番新しいのでも入れてみようか。

と始めてみたものの configure で、fortranが見つからないんですけどって言われて、先に進むのを拒まれてしまった。

じゃフォートランを入れればいいんだな。パッケージになってるのは、flang ってやつ。能書きには、

Flang is a Fortran compiler targeting LLVM.
This package includes the compiler passes and runtime library.
Flang is based on the Nvidia PGI Fortran compiler.

LLVM系なのね。取り合えず入れてみたけど、GNU R にはお気に召さないようで、全く無視されてしまったわい。あくまでgccですかい! なんたって、コンパイラー・コレクションですから、老舗ですから。

だったら、OpenBSDのパッケージになってるRはどうしてるの? Makefileを覗くと

COMPILER =              base-clang ports-gcc base-gcc

と、コンパイラーの揃い踏みを要求。どうも使ってるのは、 MODFORTRAN_COMPILER = gfortran モジュール用途みたい。

面倒嫌いなオイラーは、素直にパッケージから入れる事にした。

doas pkg_add R
quirks-3.326 signed on 2020-09-22T20:03:15Z
R-3.6.3:gtar-1.32p1: ok
R-3.6.3:g95-8.3.0p5: ok
R-3.6.3:texlive_mktexlsr-2019: ok
R-3.6.3:texlive_texmf-buildset-2019: ok
R-3.6.3:texlive_synctex-2019: ok
R-3.6.3:potrace-1.16: ok
R-3.6.3:ffcall-1.10p5: ok
R-3.6.3:clisp-2.49p5: ok
R-3.6.3:detex-2.8.1: ok
R-3.6.3:zziplib-0.13.62p1: ok
R-3.6.3:harfbuzz-icu-2.6.5: ok
R-3.6.3:gdbm-1.18.1p0: ok
R-3.6.3:libdaemon-0.14p1: ok
R-3.6.3:libevent-2.1.11: ok
R-3.6.3:dbus-daemon-launch-helper-1.12.16p2: ok
R-3.6.3:avahi-0.8: ok
R-3.6.3:cups-libs-2.3.3: ok
R-3.6.3:ghostscript-fonts-8.11p3: ok
R-3.6.3:ijs-0.35p3: ok
R-3.6.3:jbig2dec-0.11: ok
R-3.6.3:ghostscript-9.07p7: ok
R-3.6.3:ps2eps-1.68p0: ok
R-3.6.3:p5-IPC-Run3-0.048: ok
R-3.6.3:libpaper-1.1.28: ok
R-3.6.3:psutils-2.03p0: ok
R-3.6.3:t1utils-1.41p0: ok
R-3.6.3:dvi2tty-5.3.1p0: ok
R-3.6.3:texlive_base-2019p0: ok
R-3.6.3:texinfo-6.5p4: ok
R-3.6.3:zip-3.0p1: ok
R-3.6.3: ok
Running tags: ok
The following new rcscripts were installed: /etc/rc.d/avahi_daemon /etc/rc.d/ava
hi_dnsconfd
See rcctl(8) for details.
New and changed readme(s):
        /usr/local/share/doc/pkg-readmes/R
        /usr/local/share/doc/pkg-readmes/avahi
        /usr/local/share/doc/pkg-readmes/texlive_base
--- +ghostscript-fonts-8.11p3 -------------------
You may wish to update your font path for /usr/local/share/fonts/ghostscript

なんで、こんなのが入って来るのってpkgも名を連ねている。 その筆頭はg95って怪しいやつ。大体想像が付くけど、中身を確認しとく。

ob$ egfortran -v
Using built-in specs.
COLLECT_GCC=egfortran
COLLECT_LTO_WRAPPER=/usr/local/libexec/gcc/x86_64-unknown-openbsd6.7/8.3.0/lto-w
rapper
Target: x86_64-unknown-openbsd6.7
Configured with: /usr/obj/ports/gcc-8.3.0/gcc-8.3.0/configure ...

こんなのを自前でコンパイルしてたら、日が暮れちゃうぞと。まあ、フォートランが手に入ったんで、最新のRもスラスラとコンパイル出来るかな。gcc-8.3 は、パッケージ名で、実体は、egccになってるな。egdbと言い、GNUの拡張は、頭にeを付けるのがOpenBSDの掟なのか。

install.packages

R は入ったけど、裸のそれではほとんど使い道が無いだろう。ってか、前回の最後に記載した、tidyverseの実習をやってみたかったのよ。

debinanに入れたそれでは、データの形式を整えるところから始めよう。マーケターが1からRを勉強します【第3回】 に出て来る、 pivot_longerpivot_wider が使えなかったんだ。 調べてみると、【tidyr】gather?, spread? もう古い。時代はpivot に、行き着いたんだ。

設計ミスが有ったんで、新しいリリースで修正したとな。まるでpython2 -> python3 みたいな遍歴な訳ね。だったら今後の事も有るんで、CRANから入れてみましょ(debianな時は、面倒なのでaptで入れてた)。

素から入れたら、どんな負荷がかかるかも確認出来るからね。Windowsとかだとユーザーが多いので、多分バイナリでの提供だろうけど、辺境のOSであるOpenBSDでは、望むべきでは無い。

一般ユーザーでRを起動して install.package しちゃうと、/usr/local/lib/R/ 以下に書き込み権限が無いので、自HOMEに入っちゃう。これを嫌って、doas R で起動した環境から試してみる。

お試しで簡単そうな install.package('hms') を試した。最初にどこのCRANを使うねんって質問が出てきたので、リストからJapanを選択。簡単に入ったな。余勢を駆って、tidyrとかを入れる。

> install.packages('tidyr')
also installing the dependency 'cpp11'

trying URL 'https://cran.ism.ac.jp/src/contrib/cpp11_0.2.1.tar.gz'
Content type 'application/x-gzip' length 198184 bytes (193 KB)
==================================================
downloaded 193 KB

trying URL 'https://cran.ism.ac.jp/src/contrib/tidyr_1.1.2.tar.gz'
Content type 'application/x-gzip' length 881544 bytes (860 KB)
==================================================
downloaded 860 KB

 * installing *source* package 'cpp11' ...
 ** package 'cpp11' successfully unpacked and MD5 sums checked
 ** using staged installation
 ** R
 ** inst
 ** byte-compile and prepare package for lazy loading
 :
 * installing *source* package 'tidyr' ...
 ** package 'tidyr' successfully unpacked and MD5 sums checked
 ** using staged installation
 ** libs
c++ -std=gnu++11 -I"/usr/local/lib/R/include" -DNDEBUG  -I"/usr/local/lib/R/library/cpp11/include" -I/usr/local/include  -fpic  -O2 -pipe  -c cpp11.cpp -o cpp11.o
 :
 DONE (tidyr)

The downloaded source packages are in
        '/tmp/Rtmp0VdCtd/downloaded_packages'
Updating HTML index of packages in '.Library'
Making 'packages.html' ... done

clangが普通に使われているな。それから、/tmpの下にDLしたソースが残っている。Rを終了しちゃうと消えてしまうので、必要ならバックアップしておこう。

ob$ ls Rtmp0VdCtd/downloaded_packages/
R6_2.4.1.tar.gz            fansi_0.4.1.tar.gz         purrr_0.3.4.tar.gz
assertthat_0.2.1.tar.gz    generics_0.0.2.tar.gz      rlang_0.4.7.tar.gz
cli_2.0.2.tar.gz           glue_1.4.2.tar.gz          tibble_3.0.3.tar.gz
cpp11_0.2.1.tar.gz         hms_0.5.3.tar.gz           tidyr_1.1.2.tar.gz
crayon_1.3.4.tar.gz        lifecycle_0.2.0.tar.gz     tidyselect_1.1.0.tar.gz
digest_0.6.25.tar.gz       magrittr_1.5.tar.gz        utf8_1.1.4.tar.gz
dplyr_1.0.2.tar.gz         pillar_1.4.6.tar.gz        vctrs_0.3.4.tar.gz
ellipsis_0.3.1.tar.gz      pkgconfig_2.0.3.tar.gz

これ、hms,tidyr,dplyrをインストールした時に、お取り寄せされたもの。ggplot2とかは、使いこなせていないので、まだ入れていない。気が向いたら、そのうちに。

ess

emacsにessでも入れておくか。最新のessは補完もばっちりらしい。 companyとuse-package(init.el用)を取り寄せ。

(use-package company
 :config
 (global-company-mode)
 (setq company-idle-delay 0.1
       company-minimum-prefix-length 3
       company-selection-wrap-around t)

 (bind-keys :map company-mode-map
            ("C-i" . company-complete))
 (bind-keys :map company-active-map
            ("C-n" . company-select-next)
            ("C-p" . company-select-previous)
            ("C-s" . company-search-words-regexp))
 (bind-keys :map company-search-map
            ("C-n" . company-select-next)
            ("C-p" . company-select-previous)))

こんな設定をする。後はemacs hoge.R して、Rを動かしておけば、essとRが連携して、補完が出来るようになる。色々なemacsの設定例を見てきたけど、これに言及してるのは無かった。世間の常識なんで、誰も触れていないのかな。

; (global-company-mode)
 (add-hook 'ess-mode-hook 'company-mode)
 (add-hook 'inferior-ess-mode-hook 'company-mode)

なお、R関係だけで補完させたいなら、上記のように修正する。ess-mode-hockは、編集バッファーで有効、inferior-..の方は、Rのプロンプトが有る方での有効化だ。

ちょいと余談になるけど、特定なmodeでcompanyを有効にする方法、すぐに使おうと思った訳ではない。最初はどこでもcompanyしてたんだ。所がそれだと、emacs+gdbの時に使い難いったらありゃしない。そんな訳で、探したのさ。そしたらpythonの例が見つかったので、それをessに適用したって訳。問題はRのプロンプト側。statusラインには、iESSってモードが出てたんで、素直にそれを設定。ダメであった。

で、オイラーは、Rのコンソール側で、C-h m して、emacs君に問い合わせ。なんて言うのが正式名称かとね。

iESS mode defined in ‘ess-r-mode.el’:
Major mode for interacting with inferior R processes.

In addition to any hooks its parent mode ‘inferior-ess-mode’ might have run,
this mode runs the hook ‘inferior-ess-r-mode-hook’, as the final or penultimate
step during initialization.

こんな具合に出てきたのでそれを設定。ぐぐると結論だけが書いてあるけど、それをコピペするだけじゃ発展性が無い。で、あえて書いてみた次第。

ob$ ps a
 :
56516 p2  S+       0:08.25 emacs hoge.R (emacs-26.3)
63690 p3  S+       0:01.14 /usr/local/lib/R/bin/exec/R --no-readline

これを見ると、R側での補完を殺しておいて、都度emacs側から補完情報を問い合わせている事が分かる。

後はお好みで、装飾をすれば良い。

(custom-set-faces
 ;; custom-set-faces was added by Custom.
 ;; If you edit it by hand, you could mess it up, so be careful.
 ;; Your init file should contain only one such instance.
 ;; If there is more than one, they won't work right.
 '(default ((t (:family "DejaVu Sans Mono" :foundry "PfEd" :slant normal :weight normal :height 136 :width normal))))
 '(company-preview ((t (:foreground "darkgray" :underline t))))
 '(company-preview-common ((t (:inherit company-preview))))
 '(company-tooltip ((t (:background "lightgray" :foreground "black"))))
 '(company-tooltip-common ((((type x)) (:inherit company-tooltip :weight bold)) (t (:inherit company-tooltip))))
 '(company-tooltip-common-selection ((((type x)) (:inherit company-tooltip-selection :weight bold)) (t (:inherit company-tooltip-selection))))
 '(company-tooltip-selection ((t (:background "blue" :foreground "white")))))

tofu

グラフを書くと、文字が豆腐になる。ちゃんと日本語に設定してないからかと思ったけど、そうでは無いようだ。オイラーに取っては、フォントと印刷は鬼門だからなあ。 でぐぐる。

Rにおける作図時のフォント設定を極める

なんだか、こんな設定をすると、ちゃんと表示した。

> par(family="Console")
> plot(1:10 ~ c(1:10) )
> curve(sin, -10, 10)

一度、グラフの窓を出したままにしておくと、豆腐文字は避けられる。窓を消してしまうと、元に戻ってしまう。

compile R

フォーフォー・フォートラン・ラン・ランも手に入った事だし、R 4.0.2でも自前で作ってみるか、ってなもんで、configure したんだけど、思わぬ伏兵が。

checking zlib.h usability... yes
checking zlib.h presence... yes
checking for zlib.h... yes
checking if zlib version >= 1.2.5... no
checking whether zlib support suffices... configure: error: zlib library and headers are required

なんと、基幹のzlibが古いとよ。どうしろと? config.log惨状もとえ参照。

configure:45212: checking zlib.h presence
configure:45212: gcc -E -I/usr/local/include  conftest.c
configure:45212: $? = 0
configure:45212: result: yes
configure:45212: checking for zlib.h
configure:45212: result: yes
configure:45222: checking if zlib version >= 1.2.5
configure:45248: gcc -o conftest  -g -O2 -I/usr/local/include  -L/usr/local/lib conftest.c -lm  -liconv -licuuc -licui18n >&5
configure:45248: $? = 0
configure:45248: ./conftest
configure:45248: $? = 1

/usr/local/側にある奴って、古いのか。

ob$ ldd /usr/local/lib/R/bin/exec/R | grep z
        0000080e67dab000 0000080e67dd9000 rlib  0    1   0      /usr/local/lib/liblzma.so.2.1
        0000080e89d79000 0000080e89d8f000 rlib  0    1   0      /usr/local/lib/libbz2.so.10.4
        0000080e7f813000 0000080e7f82e000 rlib  0    2   0      /usr/lib/libz.so.5.0

pkgから入れたやつは、/usr/lib側のやつが使われている。どんなマジックを使ってるのかな? port/math/R のエリアをうろうろ。パッチが有るな。ドンピシャパッチが用意されてた。 核心部分は、

-  if (ZLIB_VERNUM < 0x1250) {
+  if (ZLIB_VERNUM < 0x1230) {

ちょっと古いやつでもOkさせてよだ。

  cat confdefs.h - <<_ACEOF >conftest.$ac_ext
/* end confdefs.h.  */

#include <stdlib.h>
#include <string.h>
#include <zlib.h>
int main() {
#ifdef ZLIB_VERNUM
  if (ZLIB_VERNUM < 0x1250) {
    exit(1);
  }
  exit(0);
#else
  exit(1);
#endif
}

_ACEOF

ここまで分かれば、後はやるだけーーー。

ob$ ./configure --prefix=/home/sakae/MINE
 :
ob$ time gmake -j3
 :
building/updating vignettes for package 'utils' ...
gmake[1]: Leaving directory '/home/sakae/src/R-4.0.2/src/library'
   10m00.54s real     9m25.27s user     9m44.43s system
ob$ gmake install

後はおまけでinfoだな。

ob$ gmake info
gmake[1]: Entering directory '/home/sakae/src/R-4.0.2/doc'
gmake[2]: Entering directory '/home/sakae/src/R-4.0.2/doc/manual'
ERROR: 'texi2any' v5.1 or later needed but missing on your system.

残念ながら余のシステムには、そんなコマンド無かったわい。これは諦めだな。

期待してた、グラフを書いた時のtofuは、相変わらず駄目であった。未来永劫 par(…) しなきゃならんかのう。まあ、オイラーの所は特殊で、X関係の表示はWindows側に投げているんで、そのせいかも知れないけど(推定無罪ってことか)。

i386なOpenBSDでもtofuが出て来る。MobaXtermなX環境でもtofuが出て来る。OpenBSDの自前のXで試そうとしたら、VMwareでもvboxでもstartxでエラーになる。昔はちゃんと動いていたのに、、、何でだ?

Sys.getenv

ふと、Makeconfなんてファイルを見ていたんだ。(@debian /etc/R/Makeconf)。そしたら、やたらめったら、configure時に色々指定してたぞ。オイラーは、–prefix だけだったのとは大違い(少しは反省しろよ)。下記のように、頭にRが付く大文字のやつが指定されてた。これって、環境変数の埋め込みではなかろうか?

'R_PAPERSIZE=letter'
'R_SHELL=/bin/bash'

ちょいと炙り出ししてみるか(ソースが有ると、こういう楽しみが有る)。

sakae@pen:~/src/R$ find . -type f | xargs grep R_PAPERSIZE
 :
./library/profile/Rprofile.unix:papersize <- Sys.getenv("R_PAPERSIZE_USER")
./library/profile/Rprofile.unix:    else Sys.getenv("R_PAPERSIZE")

他にどんな環境変数が有るか(どのマニュアルに書いてあるか? 探すよりソース嫁)

sakae@pen:~/src/R$ find . -type f | xargs grep -h 'Sys.getenv("R_' | sed 's/.*\("R.*"\))*/\1/' | sort | uniq
"R",
"R_ARCH"
"R_ARCH",
"R_ARCH", NA_character_)
"R_ARCH" # unix only
"R_ARCH") == "/x64" &&
"R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE", "3600"
"R_BATCH" {

ちょいと絞り込みが甘いけど、点検するには十分か。それにしても、色々有るなあ。期待してたフォントファミリー関係は、見当たらず。

悔しいのでちゃんと点検しよう。 文字列の置換・抽出・検索と正規表現 を参考にするのが良いだろうけど、注意しないとGNUに毒されてしまう。よって、OpenBSDにて確認する。

find . -type f | xargs grep -h 'Sys.getenv("R_' | sed 's/.*"\(R_[^"]*\)".*/\1/' | sort | uniq | wc

R 4.0.2 で確認したら74個も検出されたぞ。どれだけ有るんや。これでも絞って、頭がRで始まる物だけにしたけど、頭が _ で始まるものも有るからなあ。

R_AVAILABLE_PACKAGES_CACHE_CONTROL_MAX_AGE
R_ENABLE_JIT

誰が使うんだと言うような長ったらしいやつが有るかと思えば、興味を引くやつも有るなあ。

上の検索で、有名な R_HOME が出てきていなかった。何でかなと思ったら、

> Sys.getenv(c("R_HOME", "R_PAPERSIZE", "R_PRINTCMD", "HOST"))
        R_HOME    R_PAPERSIZE     R_PRINTCMD           HOST
  "/usr/lib/R"       "letter" "/usr/bin/lpr"             ""

ベクターで欲しいものを渡して、一気に取り出してしまう荒技を使ってたぞ。なかなかRらしくて、笑って許したる。結果をxに受けておいて、表現を確認すると

> str(x)
 Named chr [1:4] "/usr/lib/R" "letter" "/usr/bin/lpr" ""
 - attr(*, "names")= chr [1:4] "R_HOME" "R_PAPERSIZE" "R_PRINTCMD" "HOST"
> x[1]
      R_HOME
"/usr/lib/R"
> x["R_HOME"]
      R_HOME
"/usr/lib/R"

awkも真っ青な連想配列になってる。

上のSys.getenv()は何処に載ってるかと言うと、なんとmanの中のexampleだった。本体系には表れていないので、ユーザーの便に供するものだな。Sys. して、展開してみると、結構面白いのが載ってる。例えば、Sys.info()。OSのバージョンとかが得られるので、何かの時に役に立つかも知れないし、全くの無駄知識かも知れない。

正規表現をgg(ぐぐる)してたら、面白いものに行き当たった。 シェルスクリプトはバイナリを扱えない。さてどうしよう こういうの、オイラー大好物。瓢箪から駒である。

par

> trace(par)
> par(family='console')
trace: par(family = "console")
> curve(sin, -4,4)
trace: par("pch")
trace: par("lty")
trace: par("col")
trace: par("lwd")
trace: par("ann")

ふーん。

etc