この記事はR Advent Calendar 2018の22日目の記事として書きました。
それなりにチカラを入れて書いたのですが、まだまだ推敲の余地がある状態となってしまいました。
今後、断りなく内容を加筆・改訂する場合があります。
はじめに
Rユーザー勉強会@東京 Tokyo.R1の運営用の団体口座を作りに某大手銀行窓口に行った際(平日に半休を取りました..)、1時間ほど待ってから手続き窓口に案内され、担当していただいた方にホゲホゲコレコレと書類を耳を揃えてお渡したところ、「はい。グループ名はTokyo.Rですか。はい。えーと、はい。R?この、Rって何ですか?」と聞かれて、「え?あ〜そうですね...、Rって何だろうか...」となってしまいました。くっ、これが二天一流の一寸の見切りが可能とする後の先、と思いましたね。Rって何でしょうか...。「プログラミングの勉強会?ですか。はい。それで、みなさんでお集まりになって、はい。それでえーと、何をしてるんですか?」と言われても困りますよね。「う〜ん、ワレワレはイッタイゼンタイ何をしているんだろうか...」ってなっちゃいます。
この2018年は、Rの初心者向けチュートリアルコンテンツの整備に力を入れてきました。そんな中で銀行窓口の一件は、ある意味で初心者の方が何を求めているかについて考えさせられる衝撃体験でした。そもそも初心者というのヘテロな集団で「昨日Rをインストールしました」みたいな初心者の方もいれば、「生まれて初めてプログラミングっぽい事をしたい」「C++はガチだけどRも興味があって」「Rのデフォルトで入っている関数はずっと使い続けてきて詳しいけど最近の流行についても新しく知りたい」「今の自分の課題を解決する技術については詳しいけど一歩離れると分からないから刺激が欲しい」「学生時代はMATLABを使えたのに社会人になったら高くて買えないので同じことをRでやりたい」「生涯SPSSを使い続けろというジイちゃんの遺言に嫌気がさして」などなど。実際にTokyo.Rで初心者セッションを担当し、フィードバックをいただくと、想定していた以上に多様なニーズがあるんだなと実感しました。
ニーズが多様なので、応えるための方法論も1つではないと思います。例えば、個々の技術的ニーズに対してしらみ潰し的に答えを用意していくというのはそれはそれで面白いですね。時々利用していますが、聞くときも答えるときもとても勉強になります。これについては、r-wakalang2というRオタクの皆さま(褒め言葉)が常駐しているslack workspaceがあります。経験豊かなウィザード級R術者が世を偲んで?潜伏しているので、分からないことがあるんだけど何をどう聞けば良いのか分からないけど何とか質問してみました、という文面でも(真摯な質問でかつ運に恵まれていれば)そのヒトが本当に知りたいことを読み取って答えてもらえる確率が(他の媒体よりも)高いと思います。興味のある方は覗いてみてください。他にもuriboさんがやっているRラジオなんていうのもあります3。
Tokyo.Rの初心者セッションも、そうした試みの1つとして取り組んでいます。そんなこんなでこの1年間であれこれ含めて8回ほどトークしました。スライドはここにまとめてあります。これらのトークの構成は「少し視野を広げる」「巨人の肩に乗ろう」といったコンセプトで作ってきました。今になって見返すと、技術的な資料として参照するにはノイズが多くて知りたい情報にササッとリーチできないなあと反省しています。それ用は別でまとめてみたいですが、いつになることやら。そんな事よりむしろ、あちこちで小出しにしてきたこのノイズ成分をもう少し煮詰めて煮こごりにしてみたい、という欲望(反省を生かしていないじゃないか)で書いたのがこの記事です。
裏口の話
「Rって何ですか」と聞かれた時にどう答えようかという宿題が残っていました。同じように「英語って何ですか?」と聞かれたらどう答えますか?むしろ逆ですかね。英語話者の方に「日本語って何ですか?」と聞かれる場合ですね。例えば、英語で「Hello」は日本語の「こんにちは」というのだよと答えましょうか。あるいは文法の差異、例えば基本的に英語はSVOだけど日本語はSOVだよ、と答えましょうか。こうした個別の具体的な知識をアンサーするのはr-wakalangで良いのですが、「日本語って何ですか?」という質問にはもう少し違う、いわば「日本語という言語を通じた世界の見え方や考え方」のような内容がアンサーになるのかなという気がします。研究畑にいて、異分野の一流の方と交流する機会があると、ああ、このヒトは世界をどう知覚して、どういう風景 landscapeを見ているんだろうか、とか思うことがあります。知覚と身体性からなる主観的な固有世界を環世界4と呼びますが、果たしてRという言語が作り出す環世界とはどんな風景でしょうか。
もちろん、いざ英語を「学ぶ」となったら文字、文法、単語、発音をそれぞれキチンと体系的に身につけていく必要があります。そのプロセスを経ながら実際に触れる中で少しずつ着実に、実感として英語世界を理解していくというのが王道でしょう。これはプログラミング言語の入門編が、Hello, world!を出力する事から始まるのに似ています。確率の勉強であれば確率分布の種類と数式表現と特徴について集めた章をはじめの方に持ってきましょう、という事になります。脳科学の勉強をするなら、まずは脳の解剖学的構造5を自分の海馬に叩き込む事から始まるわけです。これがいわば表玄関です。Rのチュートリアルであれば、Rのダウンロード→開発環境構築→スクリプトやファイルの管理→変数の型や種類→演算子の種類→プログラミングの基礎→...さもありなんという内容ですね。オススメは何と言ってもR-Tipsさんです。最初に開けるべき表玄関として必要十分な情報がまとまっており、学生時代からお世話になっていますが、いつになっても色あせない素晴らしい内容です。
ところで、(特に古典の)SF小説を読むのが苦手だという方は、最初の導入に延々と続く現地世界用語の連打や、世界の設定で頭がシビれてしまってそこから先に進めなくなるそうです。「惑星フニャラタは表面の80%が母なるホーゲ海で覆われ、緑あふれるフガフガ大陸の中央に位置する首都フニャは西をゲホゲホ山脈に東をタラタラ平原に囲まれた要所で...」という具合です。このあたりで本をパタンと閉じてしまう。SF中毒になるとそれだけでハァハァしてしまいます(『闇の左手』6などサイコーですね)がしかし、この導入部でつまづいてしまうと、その先に広がる豊かな宇宙を味わう事なく自分には向いていなかった、求めているものと違ったと思ってしまいがちです。一方で、不親切なSFというのも存在します。『BRAM!』7 など然もありなんですが、何の説明もないままハードな状況がガンガン出てきて引き込まれているうちに世界と位相が揃うという感覚です。「英語って何ですか?」に対して、『ABBEY ROAD』8や、『Xの悲劇』9から入って英語という言語の物の考え方や豊かさに触れる、そんな事から始める英語世界の理解があってもいいかなと思います(その後で表玄関から入っても遅くないんじゃないかな)。そんな思いで、Rプログラミングの風景(landscape with R)、Rという言語の環世界とその宇宙的に広がっている世界(tidyverse)の風景についてご紹介できればと思います。言ってしまえば、これは裏口ですね。
プログラミング言語の話
銀行窓口では担当の方とあれこれ小一時間ほどお話ししたんですが10、特に全く通じなかったのが「Rという言語」があるという感覚でした(多分最後まで伝わらなかったでしょう)。この体験は結構衝撃的で、何でだろうな〜と考え込んでしまったものです。"外"から見ると、プログラミングは「ヒトがコンピューターに命令を与える」という一方通行なイメージなのかなと思い至りました。日常的に使用する(自然)言語は、意思や意図といった自己の内部にある状態変数を外部に出力して、他者はそれを受け取って、それがキャッチボールのように時間発展して、そうやって「対話」を目指すというイメージがあります。しかし、プログラミングという単語のイメージは、電卓の1, +, 1, = のボタンを順番に押すように「一方的に命令を与え」て、「一方的に解を受け取る」というイメージがあって、その一方通行性が「言語」が持つ対話というイメージと釣り合わないのかもしれません。う〜ん、確かにRを使ってコンピューターと対話してたら結構な妖しいヒト(褒め言葉)ですね。
確かに、歴史を紐解くと、プログラミング言語はもともと「ヒトがコンピューターに命令を与える」ためだけに開発されました。古の時代、パンチカードという道具でプログラミングを行なっていたという話は聞いたことがありますが、実物は見たことないですね..。探してみると、Programming with Punched Cardsという面白い文書が公開されていました。書き出しと図を少し引用します。
It must have been about 1973. Life at IBM was good, and I was busy doing whatever it is that engineers did then. Suddenly, in the life of our project, something came up that called for a computer program that did not exist, and I was asked to create it. My boss knew I’d never written a program before; not unusual since in those days there were very few engineers who knew how to program.
[Dale Fisk, 2005](http://www.columbia.edu/cu/computinghistory/fisk.pdf)より(超訳11)それは1973年の事でした。IBMライフは充実していて、私はエンジニアと呼ばれる人々がやっていたことは何でも手を出していて忙しく働いていました。ある時、突然、私たちのプジェクトライフにこれまで存在しなかったコンピュータープログラムとか呼ばれているナニかが登場し、私はそれを作ってくれと頼まれたのでした。ボスは、私がこれまでプログラムというものを書いたことが無いのを知っていました。プログラムを書く方法を知っているエンジニアがとても少なかった時代なので、これは珍しいことではありませんでした。
じわじわ来ますね。これはツールは違えど、何というか、どこかで見たというか何というか...。「私の研究室ライフは忙しくて、充実していた。ある日、突然、ボスがジンコーチノーとか呼ばれるナニかを、」はい。やめておきましょう。
この時代の「コンピュータープログラム」は、CPUの機能を直接操作する数字の文字列である機械語(86|00|ce...)、とそれを操作するために機械語に1:1で対応する単語を割り当てたアセンブリ言語を使っていたそうです(どんなモノか解説できればいいんですが.. Hanc marginis exiguitas non caperet12)。その後、BASICの登場が1964年だそうで、僕はまだ影も形もない頃ですね。Time紙にこんな記事がありました:Fifty Years of BASIC, the Programming Language That Made Computers Personal。
BASIC wasn’t designed to change the world. “We were thinking only of Dartmouth,” says Kurtz, its surviving co-creator. (Kemeny died in 1992.) “We needed a language that could be ‘taught’ to virtually all students (and faculty) without their having to take a course.”
(超訳)BASICは世界を変えるためにデザインされたのではなかった。「我々はただダートマス(大学)の事だけを考えていたんです」と共同創設者の一人Kurtz教授は語った。「ほとんどの生徒たち(と教員)は(プログラミング教育の)コースを取ったことがないのですが、彼らに"教える"ことができる言語が我々には必要でした。」
BASICはインタプリタ言語(=ソースファイルを直接実行するタイプのプログラミング言語; Rもコレですね)として実装され広く発展し、文字通り"世界を変え"ました。インタプリタ言語(やコンパイル言語)で書かれたプログラムを実行するには、いったん機械語(もしくは一度アセンブリ言語に翻訳してから機械語)に翻訳する必要があるため、CPUの挙動を直接制御するアセンブリ言語(や機械語)に対して実行速度が遅くなります。では何故わざわざ実行速度が遅いプログラミング言語をあえて作ったのかというと、"教える"ことができるからです。では何故"教える"ことができるかというと、「自然言語に近い統語構造」を持っているからです。例えばBASICではLETというコマンドを使うと計算結果を変数に格納することができますLET C = (A*2.5)+B;
。ああ、何だか親近感がわきますね。
もう一歩、何故、自然言語に近い統語構造を持っていると"教える"ことができるのでしょうか。神経科学者に聞けば、その答えは「ヒトの脳がそう出来ているから」と答えると思います。当たり前といえば当たり前ですが、最初に言語があってヒトが生まれてきたわけではなくて、ヒトの脳が言語を生み出したはずです。
例えば最も基本的なRのコードとして、csvを読み込んでdfという変数に格納する際には下記のようなスクリプトを書きます(やっと出てきたけどやっと出てきてコレかよという)。
df <- read.csv("hoge.csv", stringsAsFactors = F)
Rだけでなく、いろいろなプログラミング言語にcsvを読み込む関数がありますので、並べてみます。
こんな感じでしょうか。個性は色々とありますが、ザックリ捉えると似ていますね。いずれも演算子を挟んで左辺に対して右辺の実行結果を代入するという書き方をします。また、通常なんらかの実行作用には名前が振られ、その後に作用対象がカッコで括られて続きます。モノの名前や作用の名前は通常英語に準じた名称が付与される事が多いですが、これらは明らかに表音文字ではなく、表意文字として捉える方が適切にみえます。どんな意味を表現しているかを分解して順に書き下して並べると下記のようになります。
# dfという変数を用意して
df
# そこに何を入れるかというと
df <-
# read.csvという関数の出力で
df <- read.csv()
# そのためにはファイルのパスを指定し、
df <- read.csv("hoge.csv")
# ついでに呪文を唱えておきましょう:文字が含まれるカラムがFactorとして認識されたくない場合
df <- read.csv("hoge.csv", stringsAsFactors = F)
具体的にはdf
というモノの名前においてディーエフという発音は(ほぼ)意味を持たない、という事です。一見英語的ですが、read.csv
も本質的には「csvを読む」という意味を表す文字列であれば何でも良いので、他言語の例に出した通りCSV.read
になったりcsvread
になったりread_csv
になったり、要はどう書いても良いのだけどRではこう書きましょうという意味表現として捉える事ができます。つまりプログラミング言語は表音文字言語ではなく、表意文字言語として捉えるのが良いという事です(あとでここに戻ります)。というよりこれほど音から離れた純粋な表意に基づく言語体系という点こそが、プログラミング言語の特殊性だと思います。超視覚的言語といっても良いかも知れません。
これに対して、例えばExcelでは言語構造は最小化されています。
先のRスクリプトと同様、このExcelによる計算過程も、声に出して読み下す事を想定していません。それに加えてExcelでは数値が格納されるコンパートメント(セルと呼ばれます)の間に視覚的に把握可能な空間構造を設定できるため、「空間的に近いものは現象的に近い」という(暗に与えられた)定常性を上手く利用した情報表現が可能となります(空間的定常性についての数学的取り扱いは以前記事にしました)。言語構造はこのコンパートメントの中に限局して存在することになり、計算結果の空間構造から隠されています。その結果、この言語構造を持たない空間構造による表現は、原則として時が止まった状態Temporary frozenにならざるを得ないという制限が生じます。このため特に時間構造を含む動的な計算過程を意識するならばExcelよりも言語構造を考慮した実装を行う方が良いハズです。
上に挙げたTimeの記事の中で、BASICに批判的だった(というか色々な言語に批判的だったようですが13)ダイクストラ教授の書いたエッセイが紹介されていました。その名も「Go To Statement Considered Harmful(BASICのGOTOコマンドは有害である)」です。趣旨のところで面白い言及があったので紹介します。
My second remark is that our intellectual powers are rather geared to master static relations and that our powers to visualize processes evolving in time are relatively poorly developed. For that reason we should do (as wise programmers aware of our limitations) our utmost to shorten the conceptual gap between the static program and the dynamic process, to make the correspondence between the program (spread out in text space) and the process (spread out in time) as trivial as possible.
(超訳)主張その2は、我々の知性はむしろ静的関係を把握するのに向いており、時間とともに発展する過程の可視化についての能力は貧弱だということだ。アタマの良いプログラマがその限界に気付いているように、我々は静的プログラミングと動的過程のギャップを最小化するべきである。それは(静的なテキスト空間に展開する)プログラムと、(時間に対して展開する)計算プロセスを可能な限り調和させる事で達成される。
これは非常に明察だと思います(主張その1もあとで出てきます)。前節で述べたように文字表現というのはそもそも情報を固定する作用があるため時間発展を含みません。書きかけの記事はいつみても常に書きかけで、ちょっと見ないうちに話がドンドン進んでいった、などという事にはならないのです(ならないかなぁ...)。これに対し、プログラムに基づいた計算プロセスは時間軸に対して展開されます。良いプログラムとは、計算プロセスの時間発展をシンプルかつ的確に表現したものだ、というのは道理ですね。今彼が生きていて、昨今のインタプリタ言語の隆盛を目にしたら何とコメントするか聞いてみたい気がします。
([公式サイト](https://www.venganza.org/)より)世界は空飛ぶスパゲッティ・モンスターが創造したという有名な皮肉14がありますが、プログラミング界隈にもスパゲッティ・コードというのがあります。こんがらがっていたりぶつ切りだったり流れがぐじゃぐじゃで説明が皆無でどことどこがどう繋がっているのかサッパリ分からないコードが延々と地平線まで続いている、というイメージです。誰もスパゲッティ・コードを書こうと思って書いているわけではありません。では何でコードが(記事も!?)スパゲッティ化するのかというと、ダイクストラ教授のおっしゃる通りで静的なテキスト空間と動的な計算プロセスの距離が最小化されていないからです。
今まさにこの記事がスパゲッティ化しないように制御するのにワタシもヒッシですが、ホモ・サピエンスは既に動的なミクロ過程と静的なマクロ過程を表現する手段を200万年かけて獲得してきました。それを言語と呼んでいます。回り道ですが、言語についてもう少し深掘りしてみましょう。
言語の話
ヒトは、自分の考えている意図や感情といった内部の状態を他者と共有するために言語を使用します。これはアナログ→デジタルの情報圧縮として機能しています。例えば「嬉しい」「悲しい」という言葉はそれぞれが包括する多様で連続的な状態に対する離散的ラベルです。この情報圧縮のエンコードが共有されている集団においては逆変換が可能です。なので「嬉しい」「悲しい」と発話する事で、その個人の内部状態は他者に伝達され共有されます。これがヒトにおける社会的コミュニケーションの基盤的な作用です。
一体全体なんでこんなシステムが発達したのでしょうか。親戚たちの社会を覗いてみます。現生の大型類人猿にはヒトの他にチンパンジー、ボノボ、ゴリラ、オランウータンがいます。いずれの種においても音声を使ったコミュニケーションは観察されていますが、(少なくとも自然状態では)ヒトほど発達した言語体系は観察されていません。しかし、集団内部で非常に親密で豊かな社会的関係を築き、維持しているように見えます。言語に頼りっきりのヒトからするとちょっと信じられませんが、言語なしにどうやって社会的関係を築き、維持しているのでしょうか。
彼らにとって社会的関係を維持するために重要な要素の1つは、身体的な接触行為です。これには毛づくろい(グルーミング)や性行動が含まれます。別に毛がモシャモシャになっているわけでは無いのに、あるいは毛づくろいをしてもらえないと健康上重大な問題が生じるわけでは無いのに、彼らは暇さえあれば互いにグルーミングをしており、詳しく観察すると群の中で個体間の親和性・序列・互恵的関係性を確認しあう手段として作用している様です(社会的グルーミング)。こうした固有の行動パタン(動物生態学ではエソグラム ethogramと呼びます)が進化的に発達し残されてきたのには理由があるはずで、恐らく最初は手の届かない背中についたムシやダニを取るなど健康上の理由でグルーミング行動が生じたとして、いつのまにかそれが目的外使用され、社会的関係の維持に用いられる様になったのでしょうか。こうした目的外使用は進化の歴史の中ではよくある事です。
何れにせよ、多くの霊長類は社会的グルーミングを通して群れの中での序列や互恵的関係を確認しあう事に多くの時間的コストを支払っており、これは子どもの頃から一生涯継続されています。群れの構成メンバーの数が多くなると社会的関係の組み合わせは指数関数的に増加するため、1対1を基本とする身体接触を介した社会的関係構築のコストが指数関数的に増加し、ペイしなくなってしまうと考えられます。そのため、1つの仮説として、群れのメンバが増加するに伴い、1対多数に対して自らの内部状態を伝達して集団に共有するために、社会的グルーミングの代替手法として音声コミュニケーションが発達したという考え方があります(社会的グルーミング仮説: このパートはS.ミズンの『歌うネアンデールタール』を参照しました。これ名著なんですが絶版なんですよね〜..)。Twitterなど見ていると、言語=社会的グルーミングという説には妙に説得力がありますね。音声言語が発達すると、自分が前の日に何を喋ったかを忘れてしまっていても、他人から「お前、昨日は〜と言っていただろう」と指摘されますよね。この「昨日の自分」が他者、すなわち自分の外側に情報として保存される事で、「今日の自分」を「昨日の自分」に参照する事が可能となり、時間的に連続した自我意識が芽生えたのではないかと思います。この自己の外部化というのは次のテーマである文字言語の発達により更に駆動されていったはずです。
文字言語の話
声と同じように図形(ここでは日本語の絵と図表を合わせた概念として図形を用います。英語のFigureに近い概念と考えていただければと思います)によっても自己の意図や感情など内部状態を伝達する事が出来ています。そのメカニズムに対する仮説の1つは、ブーバ・キキ効果15として知られています。ギザギザの図形とフワフワの図形を提示して、どちらが「ブーバ」でどちらが「キキ」の音声と対応するか、をアンケートした結果、母語(第一言語)に関わらず多くのヒトが「ギザギザ図形」と「キキ」、「フワフワ図形」と「ブーバ」を結びつけたという結果になりました。厳密な議論をさておくと、「ギザギザ図形」は集団Aでは「キキ」、集団Bでは「カクカク」、集団Cでは「ギザギザ」と呼ばれるかもしれませんが、それらは相同の擬音語(オノマトペ)に属し、共通した概念を想起させるという点で、内部状態のエンコードとして機能するという理屈です。
Ramachandran & Hubbard, 200115より
人類は古代(といってもホモ・サピエンスになってから、早くてもホモ・ネアンデルターレンシス)から図形を用いた社会的コミュニケーションを発達させてきたようです16。この時期には少なくともある程度の音声言語を獲得していたという説が概ね受け入れられています。下図左はホモ・サピエンスの洞窟壁画のレプリカ17ですが、手形・ドット・直線・曲線・図形・動物の線画などが見て取れます。図形の表現については後ほど可視化のパートでもう少し議論したいと思いますが、今の議論で着目して欲しいのは、これらの図形が明らかに意図的に配置されている点です。しかし、古代エジプトのヒエログリフ(右)のような統語構造はどうやら無さそうです。
こんなストーリィが頭に浮かびます。プリミティブな音声言語で表しきれない表現を図形を使って行う。図形が広く利用されるようになるとそれに対応する(ブーバなどの)音声単語が生じる。すると更に「音声言語で表しきれない表現」が生まれ、それを図形で表現する。こんな風にして音声言語の複雑化と、図形表現の複雑化は足並みを揃えて並行的に進み、結果的に音声言語と文字言語が生まれる。
これと合わせてもう一つのストーリィが頭に浮かびます。プリミティブな音声言語で表しきれない表現を図形を使って行う。すると図形はその場に残される。次の日に見ても「昨日の話」が情報として残されている。しかも他者からの伝聞ではなくて自分が描いた/書いたモノがそのまま静的に残っているので自己情報の外部化としてはより強力に作用したはずです。音声言語と文字言語の並行進化が創発的に作用し、自我意識が誕生したのではないかと思います。
少しまとめますと、ホモ・サピエンスは200万年以上前から遺伝学的な生物種としては同じでも、音声言語・文字言語を介した自己情報の外部化(外部固定)による時空間的に連続した自意識を獲得し、その概念を世代を超えて受け継いでいく事で、ほとんど全く異なった環世界を獲得してきた、というのがkm仮説としておきましょう(同じことを既に誰か考えているでしょうけど)。とすると、我々が「意識」あるいは「思考」と呼んでいるプロセスは言語を離れる事が出来ないのは、ほとんど自明に思えます。
ここではあえて文字言語と一括りにしてきましたが、この流れなら、文字言語は恐らく最初は表意文字として誕生したのではないでしょうか。例えば氷河期など環境の激変をきっかけにそれまで別々の表意文字を使っていた集団が合流した時や、あるいは氷河期が明けて群サイズが劇的に増加したタイミングなどが表音文字誕生のきっかけになったのかもしれません。しかしこの議論は流石に脱線し過ぎですね。ここでは最初の文字記号が恐らく表意文字だったという点で十分です。なぜなら、プログラミング言語は read.csv
の紹介で書いたように、表意文字だからです(たどり着けた)。
意識とプログラミングの話
とある宇宙人(あるいは脳科学者)が、地球からヒトを1人だけ選び出して徹底的にスミズミまで調べたとしたら、「社会性」を解けるでしょうか。僕の考えでは、ネガティブです。何故なら世界にヒトが1人だけしかいなかったら社会性もヘチマもありません。これが2人(もしくはそれ以上)になった途端、突如として社会的関係が生じます。従って、ある社会的関係を、それを構成する個人に「還元」し、あとで復元しようとしても本質的には何も分からないでしょう。つまり「社会性」と「社会性を構成する個人」には対称性の破れがあります。個の集合が突如として別の機能を獲得するこうした作用を「創発」的なプロセスと呼びます。この文章は飛行機の機内で書いていますが、部品に使われているネジをどれだけ詳細に調べたとしても飛行機が飛んでいる理由を説明できないのと似ています(飛行機が飛ぶためにはこのネジが必要だけれど、構成要素であるネジから飛行という機能を復元することは(多分)できない(ですよね?))。
この対称性の破れという観点から見た時、我々が意識と呼んでいる作用は、プログラミングとその実行プロセスに似ています。我々は、体というインターフェースを通して「コップに注がれたワインを飲もう」と意識します(マクロなプロセス)。すると前頭前皮質やら前運動皮質やら運動皮質やら皮質脊髄路やら運動神経やらが連動して電気信号を構築して特定の筋繊維が的確な順番で連動的に駆動されて右手がコップを掴んで口元に当てて中身を口腔に注ぎ込む、という事になります(ミクロなプロセス)。少なくとも我々の意識世界ではそういうストーリィになっています。こうしておくと、意識は、マクロなプロセスだけにアクセスすれば良いという利点があります。もしそうなっていない場合、ワインを飲むたびに全てのミクロなプロセスを明示的に指令しなければならなくなります(という風に単純化しますが本当は身体性という新たな問題がこの点には含まれており、以下ではその点に少し触れています)。ただし、「コップに注がれたワインを飲もう」と意識した時に、自分がどんな姿勢でコップはどんな形状でどの位置にあってどの程度の量が入っているか、に応じて「コップをとってワインを飲む」というミクロなプロセスの詳細は変わってしまいます。この場合は「あらゆるコップの取り方=ミクロな過程」が「コップを取ろうという意思=マクロな過程」に含まれている、と考えることができます。これはまるでwrapper関数ですね(wrapperについてはあとで出てきます)。
意識というマクロのレベルからみたら「飛行機が飛んでいる」なんですが、実際には原子の塊が内部で高度な複雑系として作用しています。私たちの意識は「車を運転している」と認識していますが、ネジやらシリンダーやらガソリンが何やら複雑な連動を持って駆動されているはずです。この2つのレイヤーは対称性の破れを持っています18。プログラミングというプロセスもまた、コードを考えて書いて読んで実行するという要素を持っています。っという表現は、非常に上位概念的(マクロ)な捉え方で、実際には「実行」には、僕がよく知らないRAMとかROMとかCPUとかキバンとか色々なモノと、コンパイルとかカイロにデンキが流れるとか色々なコトが含まれています(よく知りませんが)。RのGUI上で行う内容を「マクロ」な操作として、Enterキーをスターンと押した後にPCが"勝手に"やってくれる内容を「ミクロ」なプロセスと言い換えても良いかもしれません。プログラミング言語において、マクロな操作はミクロの過程を構成するのに必要な情報を100%与えます。この逆を考えると少なくとも1:1で対応している訳ではなさそうです。 A <- B
と書いても B -> A
と書いても、あるいはスペースを除いて A<-B
やA->B
と書いても、そのミクロ過程はいずれかの時点(機械言語に翻訳される時点?)で同じ作用として記述されるはずで、その操作は不可逆性を含むはずです。すなわち上位は下位の階層に対して完全な情報を与えるのに、下位から上位を構築する完全な情報を再構築することができない(可能性が発散する)。これがプログラミングにおける「称性の破れ」です。つまり、プログラミングという概念は、「車を運転する」のと同じように意識的思考のインターフェースになっている、と言い換えても良いでしょう。
先に挙げたダイクストラ教授のエッセイ"Go To Statement Considered Harmful"から再び引用します(先ほどの引用の直前のパラグラフですね)。
My first remark is that, although the programmer's activity ends when he has constructed a correct program, the process taking place under control of his program is the true subject matter of his activity, for it is this process that has to accomplish the desired effect; it is this process that in its dynamic behavior has to satisfy the desired specifications.
(超訳)主張その1は、プログラマの活動は彼が正しいプログラムを構築した時に終わるが、彼の活動が本当に目指すものは彼が書き上げたプログラムの制御下で行われるプロセスが動的挙動において求められている仕様を満たす事だ。
まさにその通りで、ミクロな動的(計算)過程を制御するために、静的に展開されたマクロな(テキスト)過程を意識のインターフェースとして用いるというのが言語の機能そのものです。プログラミング言語ではこのギャップが極めて小さいですが、自然言語では実世界における動的過程から、言語的な静的状態に情報を圧縮する際に情報量の損失がガンガン生じます。次はそのお話。
実世界性の話
「キッチンから牛乳をとってきて問題」19というのがあります。人工知能を搭載したロボットに気軽に日常的な音声言語で「キッチンから牛乳をとってきて」と呼びかけたとき、意図した動作を的確に実行するアルゴリズムをいかにしてデザインするのか、という問題です。一見簡単に見えますか?ひょっとしたら「直線距離にして最寄りのキッチン」に向かいなさいというプログラムが組まれていて、たまたまその時のロボットの位置が隣の家のキッチンに近かったら...。ヒトに同じことを呼びかけても、おもむろに隣の家のキッチンに入っていって勝手に冷蔵庫を開ける、などという事は起こりません。「我が家のキッチン」である事は明示されていないにも関わらず文脈からそれと分かりますし、キッチンの中でも冷蔵庫の中に求める牛乳がある事も分かります。更に冷蔵庫の中でも牛乳のような頻繁に取り出されるモノの配置は慣習的に決まっている事が多いので迷う事なく牛乳に到達し、それを持ってくることができるはずです。
ロボットが的確に我が家のキッチンの冷蔵庫に到達して牛乳パックを手にしたとして、「牛乳を持ってくる」が何を意味しているのかというと、液体の牛乳を持ってこようとしてビシャビシャにこぼされてはたまりません。牛乳パックごと持ってくるのか、グラスに入れて持ってくるのか、猫の餌皿に入れて持ってくるのか、ヒトであれば状況を読み取って容易に判断できますが、音声処理のみに依存して情報を抽出しようとするロボットには難しいタスクです。「我が家の冷蔵庫にある冷蔵庫の右のドアポケットにある牛乳パックを持ち、僕が座っている椅子の近くのテーブルに来て、そのテーブルの上にあるガラスコップに200mL注いで」と言えば少しはマシになりそうですが毎回そのように命令したくありませんよね。つまり「キッチンから牛乳取ってきて」という言語情報は、そのヒトが本当にしてほしい事を圧縮して出力しているのでその分情報量が欠損しているのです。これは対ヒトのコミュニケーションであれば「暗に伝わる」か、伝わらなくても「あれ?牛乳どこだっけ?」と聴き返してくれるのでそれで万事OKなわけです。
誤解の余地を無くすためには、「暗に伝える」事を廃し、必要な全ての情報を明示的に与える必要がある事がわかります(機械語プログラミングのように)。しかしそれにはコストがかかり、牛乳パックを持ってくる命令をロボットに数日かけて伝えていては本末転倒です。ここにジレンマがあります。情報を正確に厳密に伝えようとすればするほど、圧縮率は低くなり、伝達コストが増加してしまうのです。どこかで上手く折り合いをつける必要があり、プログラミング言語はそのトレードオフの中で工夫を凝らされた言語体系なのです。どこでバランスを取るのかは、それぞれのプログラミング言語において異なりますので、自分の目的に適合する情報伝達コストを持ったものを選択して用いることになります。
Rの代入演算子の話
<-と=ってどちらでもいいんだ。
— 奥住啓祐 (@taberuhanasu) 2018年12月6日
あえてRで「<-」を使うメリットってなんだろ。
Rは、伝達コストを下げて出来るだけ自然言語と同一の論理的思考をそのまま書いてそのまま読める、という事を強く意識したデザインになっています。例えば、Rの代入演算子は<-
です。多くのプログラミング言語が採用している=
を(あえて)避けています。理由は何でしょうか?A = B
と書くと、普通は変数AにBを代入する事を意味します。Aは代入される先、Bはその内容なので、AとBは非対称ですね。しかし記号=
が対称なので読み手(ヒトもしくはぱそこんさん)は記号の空間配置を手掛かりに読み解くわけです。これは「AにBを入れます」もしくは「AをBと定義します」というヒトの思考の流れを文字記号に落とすとこうなるという表現ですね。後者を意識したのか、SQLでは、:=
を使います。数学記号のイコール=
では両辺が等価なのに対し、:=
を用いる事で、右辺の式が左辺の変数の定義になっている非対称な関係を明示することができます。Rでは、代入演算子として<-
を使って、A <- B
と書きますが、一方で、B -> A
と書く事もできます。この2つが全く同じ意味を持つ、というのはプログラミング言語としては極めて珍しいと思います。非対称な代入演算子を持つSQLでもA := B
はできてもB =: A
という書き方はできません。
翻って、自然言語ではどうか。「受動態:AはBと定義される」と「能動態:BをAと定義する」は、どちらも自然ですし、意味内容も相同です。というよりも「する」が基本で「される」がその変形と考えます(これは日本語でも英語でも中国語でもそうです)。ヒトが自らの思考を文字情報に圧縮する際に、能動態でエンコードするのがより自然なのかもしれません。勿論、いわゆる日常的な思考と、情報処理に関わる思考ではより適した思考形態が異なるかもしれません。しかし、能動態と受動態という2つの思考の様式があるなら、どちらも実装できる「言語」の方がより自由である、ように思います。例えば数学や他のプログラミング言語を学んだ機会があると、プログラムを書くときに受動態的考え方をする様式が身に染み付いているので能動態的なコードを書くと、僕自身とても違和感があります。ただ、初めてプログラミング言語に触ろうというヒトがA = B
とB = A
は意味が違うと教わるよりも、A <- B
とA -> B
は同じだと教わる方が言語としてより自然だ、と感じる可能性はあります。であれば後者のプログラムの方が汎化された可読性を持っていると言っても良いのではないでしょうか。
例えば、データ解析ツールとしてExcelしか知らないヒトは、知りたい情報について、いかに上手くExcelで取り扱える様なデータを得るかに知恵を絞ります。同様に、仮説を検証する手段としてt検定しか知らなければ、いかに上手くt検定に落とし込める研究デザインを考えるか、に知恵を絞るわけです。つまり「データ解析ツールの持つ処理体系が、研究全体の思考プロセスに影響を与える」という、これはサピア・ウォーフ仮説20のデータ解析版です。受動態的思考形態の枠組みしか知らなければその中でいかに上手く勝負するかを競うわけですが、その枠組みに囚われる必然は特にありません。能動態的書式が上手く生きる場合もあります。例えば次に紹介するパイプ演算子%>%
は典型的な能動態的書式だと捉えることもできます。
Rのパイプ演算子の話
「ヒトの自然な思考の流れ」と「プログラムを読み書きする際の思考の流れ」を出来るだけ揃えることで、その二つを出来るだけ近づけよう、という設計哲学はRの多くの側面に見受けられます。特に、Hadley Wickham氏らによるtidyverseの構想ではこの側面が強調されており、その象徴的な機能の一つがパイプ演算子%>%
です。
パイプ演算子の基本的な挙動は上図の通りです。「直前の結果を第一引数(もしくはピリオド . で指定された任意番目の引数)に引き継ぐ」という機能を持っています。一見するとややこしいだけで、こんな演算子をワザワザ理解して使いこなすメリットが無いようにみえるかもしれません。ところがこれは、これまで書いてきた視点に立つと、非常にスグレモノなのです。その真価をみるために、「キッチンから牛乳をとってきて」問題に戻りましょう。ロボットという変数に対してここの動作を関数として設定し、命令を与えながらロボットをコントロールしてキッチンから牛乳を取ってきてもらいます。
ロボットを変数として、それに与える命令を一般的なプログラミング言語の作法で書き下すと下図の左側になります(実際には欲しい結果であるコップの側を変数において受動態で書くのが一般的ですが説明のためこうしています:ロボットに持ち上げられたコップ=持ち上げられる(コップ, ロボット)という感じです。分かりづらくなるのでロボットを主語にしました)。
注目する点は「①何も持ってないロボット」→「②空のコップを持ったロボット」→「③空のコップと牛乳を持ったロボット」→「④牛乳が入ったコップを持ったロボット」と、ロボットの状態が変わる度にそれを出力として定義し、次の動作の開始時点に入力する、という書き方をする点です。
一方、パイプ演算子を使うと右のように書けます。ここでは動作の出力が次の動作に引き継がれて入力されるため、「途中の状態のロボット」が見当たりません。その代わり自然言語のように「初めの状態のロボット」が「どう命令されたのか」という能動的な動作過程に絞った表現になっています。そうですよね。ロボットに「この空のコップを持って、空のコップを持った状態で冷蔵庫から牛乳を取り出して、冷蔵庫から取り出した牛乳と空のコップを持った状態で空のコップに牛乳を注いで、牛乳が注がれたコップを持った状態でこの机にそのコップを置きなさい。」といちいち命令するのは大変ですが、「この空のコップを持って冷蔵庫から取り出された牛乳を注いでから机に置いて」と言うのが随分と自然言語に近づいています。もちろん、ロボットの内部ではパイプ演算子を使っても使わなくても実行される計算プロセスは変わりません。が、そのプロセスをヒトがプログラミング言語として書き下す際に、パイプ演算子は自然言語に近いフォーマットで読み書きできる手段を提供しています。
「どうぞよろしくお願いいたします」という定型句をメールの最後に毎回打ち込むのは面倒ですね。そんな場合は事前に「ど」というショートカットを作っておいて、「ど」=「どうぞよろしくお願いいたします」という辞書を登録しておきます。そうすれば、メールの最後に「ど」と打って変換するだけなのでラクチンですね。プログラミング言語でも、ロボットに牛乳を持ってきてもらいたい時に毎回それを書き下した指示を与えるのは面倒なので、一連の動作に名前をつけた関数 functionを自分で定義しておくと便利です。Rで独自の関数を定義するには、下記のような書式で内容を定義します。
ロボよろ <- function(牛乳, コップ, ロボット, 机 = リビング机, 冷蔵庫 = 家の冷蔵庫){
ロボット %>%
持つ(コップ) %>%
取り出す(冷蔵庫, 牛乳) %>%
注ぐ(コップ, 牛乳) %>%
置く(コップ, 机) -> 結果
}
一連の動作の登場人物をfunction( )
の中に入れておいて、その中身を後に続いている { }
の中に書き込みます。関数の名前はこの場合ロボよろ
ですね。登場人物の中で「机」と「冷蔵庫」について何にも言わない場合はそれぞれ「リビング机」と「家の冷蔵庫」の事だと思って欲しい時には、このようにデフォルトの値を指定しておきます。こうしておけば、ロボよろ(牛乳, コップ, ロボット)
と言うだけで一連の動作が実行され、欲しい結果が得られます。このように動作に名前をつけてそれを手軽に呼び出せるショートカットを作っておく利点は、ロボよろ(麦茶, 水筒, ロボット)
のような使い方ができる事です。便利ですね。それが次の話題です。
記号接地の話
これは正に情報の圧縮です。プログラミング言語に関わらず、言語は本質的に情報を圧縮する機能を持っています。例えば「ラーメン」という言葉は、ありとあらゆるラーメン集合の全体にアクセスする際のショートカットとして機能しています。それは同時に、実世界に存在するありとあらゆる存在の中から、ラーメンだけを切り出してきて均一のラベルを貼る事も意味します。そして、「3.14はだいたい3だ」という時に「.14」の情報が切り捨てられるのと同じ様に「これ(札幌ラーメン)も、あれ(醤油ラーメン)も、それ(豚骨ラーメン)もだいたいラーメンだ」という過程では、共通のラーメンというラベルに圧縮される過程で個々のラーメンが持つ独自の特徴は失われます(情報量の損失)。失われた情報量を補完したい場合には「豚骨ラーメン」のように別の階層に与えられたラベルで修飾します。これは際限がないプロセスです。「吉祥寺駅の南東にある洞くつ屋21のラーメンただし麺チョイ固め油少なめ茎わかめ半熟卵トッピングでいつもの店長が湯切りをしてくれたもの」という具合に果てしなく限定し続けてるとかなり近きますが、まだ、現実の目の前にあるこのラーメンに接地 groundingしていません。言語による長い長い修飾をいくら重ねたとしても、言語は常に集合の代表として機能しているため、永遠に接地しないのです。情報量を統計的にどう取り扱うかについては、以前Qiitaに記事を書きました。
そんな小難しいこと言わずに「このラーメン」で伝わるよ。それで伝わるのは実際にその場にいるヒトだけですね。それ以外のヒトは、同様のもしくは相似のラベルに接した経験を手掛かりに近似して理解するしかありません。一方で、ラーメンという言語表現がなければ、そもそも何が目の前にあるのか簡便に近似することすらできず「これ」と言う他ありません。「コーヒー」という言語表現がなければ、「今度一緒に、焦がした豆の粉末からお湯で抽出した成分を含む黒い苦い液体を飲まない?」と言う必要があり、「焦がした豆の粉末からお湯で抽出した成分を含む黒い苦い液体?いいね。」と答えなければなりません。ここにトレードオフがあり、抽象化の程度を上げればより広い事象に対してそれらを代表するラベルを貼ることができる一方で、現実世界の個別事象に対する接地性が減少する、というジレンマが生じます。
イヌイットの言語には日本語で「雪」と表す概念を表現する名詞が3~20種類あって、それぞれ使い分けていると聞いたことがありますか?日本語で雪を表す語幹は「雪」一種類だけですが、イヌイットの言語ではこれが多数ある、という事です。面白いので脱線しますが、この数は、最初の報告では4種類だったのにその後(雪だけに!?)雪だるま的に増加しあげくには100種類まで。経緯についてはL.D. Kaplanという方が論文にしていました: "Inuit snow terms: How many and what does it mean?"。ちなみに最初に報告されたのは、以下の4種類で、これについてもまぁ色々と議論があるようです(調査対象が単一言語なのかどうか, など)。いずれにせよこれらの単語を日本語に翻訳しようとすると「雪」+状態を表す形容詞を使って派生語として表します。この「雪」部分が語幹です。そしてイヌイットの言語体系において雪を表す語幹が複数あるというのは確かなようです。
aput: snow on the ground, 積雪
qana: falling snow, 降雪
piqsiqsuq: drifting snow, 吹雪いている雪
qimuqsuq: snowdrift, 吹きだまった雪
この例はサピア・ウォーフの仮説の好事例として持ち出されることが多いですが、過剰な「盛り」には閉口します。この手の「盛り」事例は色々ありまして、スタンフォード監獄実験は嘘だったとか、新しいところではSTAP細胞事件がありました。見たいモノを見せられた時に、過剰な共感や直感的な納得により検証が緩くなってしまう事には気をつけたいところです。もちろん、小説家Margaret Atwoodの有名な言葉として知られる言葉22は、後半のオチに結ぶセンスの良いフィクションとして、目くじら立てずに楽しんだら良いのでは、と思います23。
"The Eskimos had fifty-two names for snow because it was important to them, there ought to be as many for love." -- Margaret Atwood
脱線しましたが、これは日本においては、4つの状態の「雪」が生活に密着した独立した概念として切り出されなかった事を示しています。もっと身近な例では、日本にはウシを表す言葉は「牛」だけですが、英語では「雄牛」「仔牛」「牛」それぞれに別の単語(語幹)があるのも同じです24, 25。日本においては「このウシ」を単に「牛」として表しても情報の損失がそれほど大きくないため、それで十分、という訳です。このギャップが大きければ、間を埋めるのに適切な細分化されたウシのステータスに応じた語幹が生み出され、受け継がれていきます。必要ない単語を誰かが生み出したとしても時代とともに絶滅していき、必要な単語が最初は様々な呼ばれ方をしていたとしても次第にwinner-takes-allに収束していくと考えられます。
cattle: 畜牛(畜産動物としての牛の総称)
cow: 雌牛, 乳牛に用いるが牛の一般呼称としても用いる
bull: 去勢していない雄牛
bullock: 去勢した雄牛(主にアメリカ英語)
steer: 去勢した雄牛(食肉用)
ox: 去勢した雄牛(荷車用)
calf: 仔牛
Rでも同じですね。Rの機能を拡張するパッケージ(アドインのようなものです)は、誰でも開発して公開することができます。世界中の人々が様々な拡張機能を実装したRパッケージを公開しており、この記事を書いている2018年12月時点で、github上で公開されているものは6万種類を超えています26。広く使われるパッケージは長く生存し、同じ内容を実装している複数のパッケージがあれば統合されたり1つが選ばれていきます。
過渡期的状況としては、例えばRの画像処理パッケージはbioconductor謹製のEBImage
、お手軽画像加工のmagick
、そして器用だけどカユいところもあるimager
という三者揃い踏みが続いています。
混乱の元なのでユーザーとしてはもう少しなんとかならんのかという感じもしますね。例えば、以前データの入出力周りのトークの際にお話ししましたが、画像をオブジェクトに読み込む時の関数を取り上げると、(conflictを避けるという意味では当然なのですが)パッケージによって全部表現が違います。
しかし、この様にRにおける言語進化の枠組み対する参入のハードルを下げることで、幅広いニーズに対して接地した言語体系を獲得してきたことがRの人気を支えている一因ではないでしょうか。
Rにおける言語的抽象性の話
言語によって世界からコーヒーを「コーヒー」として切り出してくることと、プログラミング言語によって独自に変数A
や関数ロボよろ
を定義する事は、全く同じ作用なので、同じジレンマが生じるはずです。何も持っていないロボット
とコップを持ったロボット
に異なるラベルを貼るというのは、自然言語に比べて抽象化が足りないんじゃないかという気になりませんか?つまりパイプ演算子を使う事で、自分が書いているプログラムの言語的抽象性をある程度制御する事ができます。
パイプ演算子以外にも、Rではこの言語的抽象性の取り扱いを「クラス」と「メソッド」という概念で実装しています。まぁ折角なので、少しRのコードを打ってみますか。とりあえず適当なサンプルデータを用意します。
set.seed(71)
N <- 20
x <- seq(0, 1, length = N)
y <- 2 * x + rnorm(N, 0, 1)
dat <- data.frame(x, y)
これで変数datは、2つのカラム(xとy)を持ち、それぞれ順番で対応づけられた20個の値を持った行を持つ行列(のような)内容で定義されました。中身を見るには、head
やstr
といった関数を使います。
# データの先頭を表示
head(dat)
<実行結果>
> head(dat)
x y
1 0.00000000 -0.431842186
2 0.05263158 -0.341924019
3 0.10526316 -0.268046272
4 0.15789474 0.732934878
5 0.21052632 0.003151991
6 0.26315789 -0.660848129
# データの構造を表示
str(dat)
<実行結果>
> str(dat)
'data.frame': 20 obs. of 2 variables:
$ x: num 0 0.0526 0.1053 0.1579 0.2105 ...
$ y: num -0.43184 -0.34192 -0.26805 0.73293 0.00315 ...
str(dat)の実行結果は、変数datはdata.frameという種類に分類され、20個のobservations(観測値)が2種類のvariables(変数)について与えられた結果が格納されています。variablesはそれぞれxとyで、xはnumber(数値)で先頭は...、yはnumber(数値)で先頭は...です、という情報を表しています。
んでは、これを線形回帰した結果をみようとすると...
dat_lm <- lm(y ~ x, data = dat)
str(dat_lm)
こうですね。
結果、dat_lm
はこんな感じの構造になっています。
> str(dat_lm)
List of 12
$ coefficients : Named num [1:2] -0.386 2.431
..- attr(*, "names")= chr [1:2] "(Intercept)" "x"
$ residuals : Named num [1:20] -0.0456 -0.0836 -0.1376 0.7354 -0.1223 ...
..- attr(*, "names")= chr [1:20] "1" "2" "3" "4" ...
$ effects : Named num [1:20] -3.708 3.299 -0.116 0.755 -0.105 ...
..- attr(*, "names")= chr [1:20] "(Intercept)" "x" "" "" ...
...
12個の名前のついた要素の集合(List)で、それぞれ数字や文字やモデルが格納されているようです。
名前だけにアクセスしたい時にはnames
を使います。
> names(dat_lm)
[1] "coefficients" "residuals" "effects" "rank"
[5] "fitted.values" "assign" "qr" "df.residual"
[9] "xlevels" "call" "terms" "model"
何となく線形回帰の登場人物が揃い踏みという感がありますね。
ここでは、この詳細を説明しようというわけではありません。
その代わり、例えばdat
をplot
してみようと思うと、
plot(dat)
dat_lm
の方はどうかというと、
plot(dat_lm)
こんな感じの4枚の図が順番に表示されると思います。
んんーどうでしょう、普通ですか?変ですか?凄いですか?
dat
とdat_lm
の中身は全然違いました。dat
は2列10行のマトリックスをdata.frame
という形式で整えたものですし、dat_lm
は長さも属性も様々な12個の要素からなるlist
でした。plot
関数は、それぞれに応じて出力する図を切り替えているように見えます。賢いじゃないですか。
仕組みは上の図のようになっていて、こうしておくことで「これ作って」という札を持っていくだけで、だったりだったり、札にあった物が出てくるというユーザーフレンドリーな実装になっています。赤い札を持って行った時だけ「めんかた油少なめ」というオプションが評価されるという事になります。つまりplot
という関数は描画に関する総合受付窓口として機能しているわけです(総称関数generic-functionと呼びます)。入力値の属性(classと呼びます)それぞれに応じた描画方法(methodと呼びます)を呼び出して、出力を作っているという理屈です27。入力値の属性を確認する関数がclass
で、関数が持っているmethodを確認する方法がmethods
です。classに応じてどのmethodが呼び出されているかはgetS3method
で確かめる事ができます。
> class(dat)
[1] "data.frame"
> class(dat_lm)
[1] "lm"
> methods(plot) # plotは入力classに応じて沢山のmethodを持っています
[1] plot,ANY-method plot,color-method plot.ACF*
[4] plot.HoltWinters* plot.R6* plot.TukeyHSD*
[7] plot.Variogram* plot.acf* plot.augPred*
[10] plot.compareFits* plot.data.frame* plot.decomposed.ts*
[13] plot.default plot.dendrogram* plot.density*
[16] plot.ecdf plot.factor* plot.formula*
[19] plot.function plot.ggplot* plot.gls*
[22] plot.gtable* plot.hclust* plot.histogram*
[25] plot.intervals.lmList* plot.irt* plot.isoreg*
[28] plot.lm* plot.lmList* plot.lme*
[31] plot.medpolish* plot.mlm* plot.nffGroupedData*
[34] plot.nfnGroupedData* plot.nls* plot.nmGroupedData*
[37] plot.pdMat* plot.poly* plot.poly.parallel*
[40] plot.ppr* plot.prcomp* plot.princomp*
[43] plot.profile.nls* plot.psych* plot.ranef.lmList*
[46] plot.ranef.lme* plot.raster* plot.residuals*
[49] plot.shingle* plot.simulate.lme* plot.spec*
[52] plot.stepfun plot.stl* plot.table*
[55] plot.trellis* plot.ts plot.tskernel*
> getS3method(f = "plot", class = "data.frame")
function (x, ...)
{
plot2 <- function(x, xlab = names(x)[1L], ylab = names(x)[2L], ...)
plot(x[[1L]], x[[2L]], xlab = xlab, ylab = ylab, ...) # ←最終的に呼び出されるのはコレ
if (!is.data.frame(x))
stop("'plot.data.frame' applied to non data frame")
if (ncol(x) == 1) {
x1 <- x[[1L]]
cl <- class(x1)
if (cl %in% c("integer", "numeric"))
stripchart(x1, ...)
else plot(x1, ...)
}
else if (ncol(x) == 2) {
plot2(x, ...) # ← ココですね。
}
else {
pairs(data.matrix(x), ...)
}
}
関数の話
話を少し巻き戻します。パイプ演算子がイイという話をしました。何で良いかというと、自然言語的な思考の流れとプログラミング言語の読み書きの流れが揃うから、でした。この図においてもう一つ強調したい点があります。それは「関数は動詞だ」という事です。当たり前ですか?うーん。状態Aを状態Bに変化させる、それは動詞ですね。ただここで言いたいのは、むしろどちらかというと状態Aを状態Bに変化させるという「動詞は常に関数として扱おう」という事です。
この原則は一見当たり前に見えるんですが、例えばデータの一部を抽出する操作は、ついつい名詞的な(視覚的な)操作の工夫で書いてしまいがちです。こう書くわけですね。
dat_2 <- dat[x >= 0.5, ]
実用上はこれで運用可能ですし、実際、変数dat
の中での空間配置を考量して行列の中の要素にアクセスしたり抽出する際には、多くのプログラミング言語でこの類の操作を同様の書式で行います。
これはこれでいいんですが、しかしよく考えるとdat
からその一部であるdat_2
を取り出すというのはどう考えても動詞なので、やっぱ動詞的に、すなわち関数として扱う方が、自然言語の環世界からすると違和感が少ないように思います。tidyverseでは、あえて明示的に動詞として関数を使います。例えばデータの特定条件を満たす行は下記のように取り出します。
dat_2 <- filter(dat, x >= 0.5)
第一引数がdat
であることに注意して、前出のパイプ演算子を使うと、こう書きます。
dat_2 <- dat %>%
filter(x >= 0.5)
# 比較のため再掲。
dat_2 <- dat[x >= 0.5, ]
これら2つのコードは同じ機能を持っていますが、filter
という関数を使う事で、このコードが動作を表している事が明確化されます。後者ではdat
というモノの一部に新しいdat_2
というラベルを振るという扱いで、この場合、動詞的作用は代入演算子に込められています。もちろん(Rでは)代入演算子<-
も立派な関数=動詞なのでスジは通っているんです。しかし<-
は代入するという動詞なので一部を取り出すという動作と必ずしも1:1対応しないという問題点があり、ここでどんな作用が起きているかは右辺の詳細を追わないと読み取れなくなってしまう訳です。
filter
関数を使った表記のメリットはここにあり、filter
を使っている時点で、何らかの行成分の部分抽出を行なったな、という内容を読み取る事ができます。実用上では、ロボよろ
の定義の際にみたようにパイプ演算子をつなげて書く場合は、途中に名詞的取り扱いが挟まると途端に流れが悪くなってしまうのを回避したい、という思惑もあります。
この議論をもう少し煮詰めますが、名詞的取り扱いの利点は「行方向のソート」という動作に対して新しい名前(filter
)を与えずに行えた、という点です。不要な抽象ラベルを減らすという観点では効率的です。逆に言えば、動作に対してラベルを貼る際に、できるだけミニマムな数に絞り込んでおかないと細分化されたラベルだらけになってしまい収集がつかない、という問題点が生じます。という訳で、tidyverseの宇宙において1つのデータに対する基本的な動詞 varb の種類は下記の5種類に絞り込まれています(これらはほぼ直感=自然言語的な予想に反しない挙動をすると思います)。また、動詞には文脈に合わせた活用形があるように、これらのverb functionsにも活用形があり、特に使用頻度が高いのは右側に書いたの3種類です。"活用形"はいずれもwrap関数なのでその機能は基本的には全て基本動詞を用いて実装させられるはずです。従って、勉強する際には、まずは基本動詞の使い方をしっかりと理解しましょう。
# 基本動詞
> filter
function (.data, ...)
{
UseMethod("filter")
}
<environment: namespace:dplyr>
# wrapper関数
> filter_at
function (.tbl, .vars, .vars_predicate)
{
syms <- tbl_at_syms(.tbl, .vars, .include_group_vars = TRUE)
pred <- apply_filter_syms(.vars_predicate, syms, .tbl)
filter(.tbl, !(!pred)) # ←ここで呼び出しているfilterが最終的に適用されています。
}
<environment: namespace:dplyr>
繰り返しの話
先ほど作った ロボよろ
関数は、ロボよろ(麦茶, 青グラス, ロボット)
という書式で書くと、「このロボットが麦茶をグラスに入れて持ってきてくれる」という動作をコードしていました。よくある事ですが、ロボットに「麦茶と牛乳とコーヒーを、それぞれ青グラスと緑グラスと赤グラスに入れて」と頼みたくなったとします。普通のプログラミング言語のお作法では、こうした場合には ロボよろ
を3回繰り返して使います。Rにもfor文があります。
右側に書いたのが典型的なfor文記法です。最初に空の 結果
を用意しておいて、繰り返し操作に与えられたタグのi
に従ったそれぞれの結果i
を追加していく事で、最終的に欲しい結果
を得る、という考え方です。 実にプログラミング言語的な表記で馴染み深いと思いますが、自然言語的な考え方では、このプロセスは3つの動作ではなくて1つの動作=関数で書きたくないですか?「麦茶と牛乳とコーヒーを、それぞれ青グラスと緑グラスと赤グラスに入れて」という訳で、「麦茶を青グラスに入れて、牛乳を緑グラスに入れて、それからコーヒーを赤グラスに入れてくれ」とは言わない、という事です。つまり"結果i"というのは自然言語的概念からすると不自然なわけです。もし"自然な言い回し"をストレート・フォアードに書ければ、読むのも楽になりそうです(ダイクストラ教授には、計算プロセスと対応づけられない書き方は邪道だと言われそうですが思考のプロセスと対応づけられているというメリットを主張して反論しましょう)。
Tidyverseの宇宙の中では、purrrパッケージがこの繰り返し処理のコーディングを担当しています。map
関数およびその派生関数がこれを実装しますが、ここでは入力値が液体とグラスの2つなのでmap2
族を使い、結果をdata.frameで欲しい場合はmap2_df
を選択するのが良さそうです。これを使うと、上記のfor文と同じ処理を下記のように書けます。map2_df
関数に入力しているのが「液体」「グラス」という集合である事と、出力もまた結果の集合であって、個々の途中段階という非自然言語的な概念(ここではi
)を読み書きするストレスを出来るだけ減らそうという設計思想が読み取れます。意見が分かれるところかもしれません。for文が書いてあった方が明示的に繰り返し処理をしている事が読み取れて嬉しい、という方もいると思います。purrrパッケージはとても便利ですが、印象としては今後もう少し関数の整理や最適化が進むかなと思っています。計算処理の高速化には並列化版のfurrrパッケージが便利でした(atusyさんの紹介記事があります)28。
構造の話
これまで挙げてきたtidyverseを中心とした「自然言語的な発想で読み書きできるプログラミング言語」としての基本設計は主に操作に関するtipsでした。これに合わせて操作される側のオブジェクトに関しても適した様式に揃えましょうという事になります。それがtidyなデータセットという考え方です29。1行1オブザベーションという考え方で、1回のオブザベーションで観察された特徴量をカラム方向に展開しましょうというお話です。1つの物体が持つ特徴量をヒトの脳がどう展開するのか考えてみると、物体が何かという情報は第一次視覚野から側頭葉に向けて信号が伝達されるに従ってより高次化(抽象化)されていくと考えられていますが、そのプロセスで「このリンゴは赤い」と「このリンゴは丸い」という情報は脳の中で並行して処理されます。これは物体検出系の人工ニューラルネットワークでも同じです。
> head(iris)
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
2 4.9 3.0 1.4 0.2 setosa
3 4.7 3.2 1.3 0.2 setosa
4 4.6 3.1 1.5 0.2 setosa
5 5.0 3.6 1.4 0.2 setosa
6 5.4 3.9 1.7 0.4 setosa
上は、tidyなデータセットになっています。行=観察、カラム=特徴量。これを特徴量を横方向に展開していると表現しましょう。ところがデータ処理のプロセスでは、これでは済まないのです。特にtidyverseで可視化を担っているggplot2パッケージではデータは縦に展開しておく方が便利です。
> iris %>%
+ gather(key, value, -c(Species)) %>%
+ head
Species key value
1 setosa Sepal.Length 5.1
2 setosa Sepal.Length 4.9
3 setosa Sepal.Length 4.7
4 setosa Sepal.Length 4.6
5 setosa Sepal.Length 5.0
6 setosa Sepal.Length 5.4
これを用いて、
iris %>%
gather(key, value, -c(Species)) %>%
ggplot(aes(Species, value, color = Species))+
geom_boxplot()+
facet_wrap(~key)
横持ちを縦持ちに変換している gather
関数はtidyパッケージに含まれています。これは非常に便利ですが、問題の1つは、時としてこのgather
を用いた横→縦変換が不可逆反応になってしまう事です。逆過程は spread
関数を用いますが、上の例で逆変換を行おうとすると...
> iris %>%
+ gather(key, value, -c(Species)) %>%
+ spread(key, value)
Error: Duplicate identifiers for rows
できません。何故か。
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 setosa
この行で(暗に)対応づけられていたオブザベーションの情報(これらのガクや花弁の長さや幅が同一の花かえら得られたという対応づけ)がgather
で縦持ちに変換する際に失われてしまっているんですね。なのでその情報をあらかじめ与えておけば、横→縦→横が全て可逆的に行えます。
> iris %>%
+ rowid_to_column() %>%
+ gather(key, value, -c(Species, rowid)) %>%
+ spread(key, value) %>%
+ head
rowid Species Petal.Length Petal.Width Sepal.Length Sepal.Width
1 1 setosa 1.4 0.2 5.1 3.5
2 2 setosa 1.4 0.2 4.9 3.0
3 3 setosa 1.3 0.2 4.7 3.2
4 4 setosa 1.5 0.2 4.6 3.1
5 5 setosa 1.4 0.2 5.0 3.6
6 6 setosa 1.7 0.4 5.4 3.9
こんな感じです。まずはメデタシですね。
この縦横変換でデータを成形していく過程は、どちらかというと空間構造をいじるイメージで行うので最初はとっつきにくいかもしれません。ついでにもう一つ、tidyverseで用いられるデータの持ち方を紹介します。
> iris %>%
+ group_by(Species) %>%
+ nest()
# A tibble: 3 x 2
Species data
<fct> <list>
1 setosa <tibble [50 x 4]>
2 versicolor <tibble [50 x 4]>
3 virginica <tibble [50 x 4]>
更に、str
を使って構造を表示すると下記のようになります。
Classes 'tbl_df', 'tbl' and 'data.frame': 3 obs. of 2 variables:
$ Species: Factor w/ 3 levels "setosa","versicolor",..: 1 2 3
$ data :List of 3
..$ :Classes 'tbl_df', 'tbl' and 'data.frame': 50 obs. of 4 variables:
.. ..$ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
.. ..$ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
.. ..$ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
.. ..$ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
..$ :Classes 'tbl_df', 'tbl' and 'data.frame': 50 obs. of 4 variables:
.. ..$ Sepal.Length: num 7 6.4 6.9 5.5 6.5 5.7 6.3 4.9 6.6 5.2 ...
.. ..$ Sepal.Width : num 3.2 3.2 3.1 2.3 2.8 2.8 3.3 2.4 2.9 2.7 ...
.. ..$ Petal.Length: num 4.7 4.5 4.9 4 4.6 4.5 4.7 3.3 4.6 3.9 ...
.. ..$ Petal.Width : num 1.4 1.5 1.5 1.3 1.5 1.3 1.6 1 1.3 1.4 ...
..$ :Classes 'tbl_df', 'tbl' and 'data.frame': 50 obs. of 4 variables:
.. ..$ Sepal.Length: num 6.3 5.8 7.1 6.3 6.5 7.6 4.9 7.3 6.7 7.2 ...
.. ..$ Sepal.Width : num 3.3 2.7 3 2.9 3 3 2.5 2.9 2.5 3.6 ...
.. ..$ Petal.Length: num 6 5.1 5.9 5.6 5.8 6.6 4.5 6.3 5.8 6.1 ...
.. ..$ Petal.Width : num 2.5 1.9 2.1 1.8 2.2 2.1 1.7 1.8 1.8 2.5 ...
こうですね。これはこれでtidyなデータと言えます。Speciesカラムに対して、それに対応する50個ずつのデータがdataカラムに格納されている、と読む訳です。ポイントは、(エクセルで言うところのセルにあたる)マトリックスにおける1つの座標に50トラックx4パラメータのデータが圧縮されて(入れ子になっている=nest)収まっていると言う点です。階層構造を持っていると言っても良いですね。圧縮されている情報の中身がみたければ、$data
にアクセスして例えば1番目を見るには下記のようにします。
> dat <- iris %>%
+ group_by(Species) %>%
+ nest()
>
> dat$data[[1]] %>%
+ head
# A tibble: 6 x 4
Sepal.Length Sepal.Width Petal.Length Petal.Width
<dbl> <dbl> <dbl> <dbl>
1 5.1 3.5 1.4 0.2
2 4.9 3 1.4 0.2
3 4.7 3.2 1.3 0.2
4 4.6 3.1 1.5 0.2
5 5 3.6 1.4 0.2
6 5.4 3.9 1.7 0.4
この様に具体的な数値を確認するには一手間かかるので何に使えるか分からん、とお思いでしょうか。このタイプのデータと上で紹介したmap
系のデータ操作関数は非常に相性が良いといのがポイントです。
> dat %>%
+ mutate(mean = map(data, ~summarise_all(., mean)))
# A tibble: 3 x 3
Species data mean
<fct> <list> <list>
1 setosa <tibble [50 x 4]> <tibble [1 x 4]>
2 versicolor <tibble [50 x 4]> <tibble [1 x 4]>
3 virginica <tibble [50 x 4]> <tibble [1 x 4]>
この操作が何をしているか、直感的に把握できるでしょうか?mutate
は、新しいカラムを作る操作です。そのカラムの長さ(行数)は元のdat
に縛られた3行になるはずですね。で、新しいカラムに何を格納するかと言うと、 data
カラムそれぞれに対して summarise_all
を実行した結果です。これは全てのカラムに対して要約した情報量を取り出す関数でした。で、どんな情報量を取り出すかというのが mean
ですね。結果、それぞれのSpecies
に対応した全てのパラメータのmean
が格納される事になります。
.$mean[[1]]
は下記の内容になります。
# A tibble: 1 x 4
Sepal.Length Sepal.Width Petal.Length Petal.Width
<dbl> <dbl> <dbl> <dbl>
1 5.01 3.43 1.46 0.246
入れ子構造を使わないでもsummarize_all
を使えば平均値を素直に取り出せます。
> iris %>% group_by(Species) %>% summarise_all(mean)
# A tibble: 3 x 5
Species Sepal.Length Sepal.Width Petal.Length Petal.Width
<fct> <dbl> <dbl> <dbl> <dbl>
1 setosa 5.01 3.43 1.46 0.246
2 versicolor 5.94 2.77 4.26 1.33
3 virginica 6.59 2.97 5.55 2.03
取り出せるんですが、このプロセスは不可逆なので元のデータに戻れない、というのが難点です。特に探索的にデータの特徴量を取り出そうとする場合、できるだけ1次情報を保持した可逆性を残しておけるのは大きな利点になります。nest
されているデータから、上の様なsummaryだけを取り出すことも可能です。同じ結果が得られていますね。例えばデータをcsvで出力したいなど考える場合、こうしてunnest
を使って入れ子構造を解除しておく必要が生じます。当然、このプロセスは不可逆な訳ですが、直前の欲しい情報をselectするまでは最初のデータが入れ子構造で保持されている事に注意してください。
> dat %>%
+ mutate(mean = map(data, ~summarise_all(., mean))) %>%
+ select(Species, mean) %>%
+ unnest()
# A tibble: 3 x 5
Species Sepal.Length Sepal.Width Petal.Length Petal.Width
<fct> <dbl> <dbl> <dbl> <dbl>
1 setosa 5.01 3.43 1.46 0.246
2 versicolor 5.94 2.77 4.26 1.33
3 virginica 6.59 2.97 5.55 2.03
tidyverseの宇宙に存在する代表的なデータ構造として3種類紹介しました。まず大原則としてこれら全てがtidyなデータであるという事=自然言語的なobservationごとに対応する特徴量の展開として整理されています(ただし縦持ちではobs IDを上手く取り扱う必要がありました)。前処理段階ではtidyな横持ちデータを作成する事を意識し、探索的データ解析においてはキーとなるタグを手掛かりにnestしながら特徴量を可逆的に抽出し、あるいは縦持ちに変化した可視化を進めます。最終的には縦持ちから作ったfigureと、nestをunnestした要約特徴量が出力されるというのが大まかな流れになります。このフローの中で自分の現在位置を自覚しながら解析を進めていく事が大切です。
可視化の話
研究者になるトーレニングでは図表を使ったトークを徹底的に仕込まれます。学生さんのプレゼンを指導!する時には「スライドを説明するんじゃダメで、スライドがあなたの主張をサポートするんだ!」とかイキった助言をしています。つまり、言語の起源の中で少し紹介しましたが、図表を使った表現は「音声表現が難しい内容」をエンコードするべきです。あなたの主張の中で何を伝えるために絵を描くのかに対して、素直に答える表現を心がけるべきでしょう。というのが最も大切なポイントです、が、良い絵とは何かについてはケース依存です。例えば上で書いた図は、主張を代表するFigureというよりも手元で再現できるシンプルなコードで描ける事を重視しています。という訳でこれ以上ここでは「絵の良さ」については触れない事にします。
Rにおける可視化には標準で入っている関数を利用する方法(左)と、tidyverseで可視化を担うggplot2パッケージを利用する方法(右)があります(この記事の中で既にどちらも登場していますね)。ちょっと機会があったので、「良い図」をggplot2を使ったバージョンと使わなかったバージョンで描いた記事を以前にQiitaで公開しました。どちらの方法を使っても出来る事はそれほど変わりませんし、また、1枚絵であれば労力もあまり変わらないと書きましたが、これからゼロから学ぶという事でしたら、ggplot2の方法論を学んでしまう方が捗るかもしれません。何故かという理由を可視化してみました。
見ればわかりますね。はい、終わり。というのでも良いのですが蛇足ながら解説します。ポイントはコードを書く順番にあります。頭の中の流れとしては、(1)空白のキャンバスを用意して、(2)そこに系列ごとの描画をして、(3)グラフィックを化粧して、(4)その図を出力する、と、こうなっています。比べてみると右側はほぼその流れに従った書き方を可能にしている事が見て取れます。条件としてはggplot関数の入力はdata.frameである事と、前節で述べた縦持ちのデータに変換しておく必要が生じる事がある、という2点です。左側の書き方では、特に(4)のgraphicデバイスを開いて閉じるという間にプロット内容を書くところがなんとも実にプログラミング言語っぽさを残しています。右のほうが自然言語的です。何度も書いてきたように、自然言語的に読み書きできると言うのはR言語、特にtidyverseを用いたRプログラミングの特色で、ggplot2パッケージは作図においてもその思想を実装したいという設計になっています。修飾部分を後付けで書けるのは、探索的な解析段階の絵は必要最小限の修飾で済ましてしまえるという利点があります。キメキメの外部に見せる絵だけ凝った作図をすればよいので、それにむけた微調整用のオプションを山ほど準備してあるという事です。
再び言語の話(結言に変えて)
ホモ・サピエンスにおいて言語機能を用いた自己情報の外部化は、自我意識の創発と深く結びついているという話をしました。Rという言葉を使って自分が知りたいことを知りそれを他人に伝える。意識や思考は時間に対して展開し、テキスト空間に展開するプログラムコード、そして計算実行過程は時間に展開し、出力される解析結果は再び空間に展開することで、再び意識に取り込まれます。この循環をメタ的に観察すると、特にR(とtidyverse)は自然言語っぽく読み書きできる様にデザインされたプログラミング言語だなぁとしみじみ感じてきます。この文章を読むと具体的に何かR言語のスキルが劇的に上昇する、はずはありません。そういう意味では入門用コンテンツとして求められるニーズに応えていないかもしれません。しかしそこは「これは裏口だから」と言い逃れるつもりです。
This slide in @hadleywickham's talk at UA today really hit home for me. It doesn't matter if you're good at math! If you can talk and read, you can probably learn to code. pic.twitter.com/EFruQGU1jG
— Ramona Walls (@Ramona_Walls) 2018年12月7日
言語、というのは本質的には世界を切り出すキマリゴトです。「あらゆる赤い色」を「赤色」と呼ぼうというこのキマリゴトは、世界を色あせたものにするでしょうか。ホモ・サピエンスが歩んできた歴史を振り返ると、「あの色」としか言えなかったソレを、「赤色」と切り出してくるこの作用が、ヒトとヒトとの共通概念を複雑化させ、自己が外部化する事で内的世界をより豊かに発展させてきたと言えそうです。Rも、また、キマリゴトです。私たちはそのキマリゴトからどんな豊かな世界を得るのでしょうか。目の前のコーディングに夢中になるのは、それはそれでとても楽しいですが、Rユーザーは世界に1人ではありません。であればそこには創発的な作用があり、さらに豊かな世界があるのではないでしょうか。何故Rが自然言語的な思考プロセスと位相を揃えた構造を持っているかというと、そんな創発的な作用をより豊かに起こしたい、起こせるんじゃないかという想いがある、というのは考えすぎでしょうか。僕はそれを「自由」になると言いたいですね。裏口から自由な世界をチラっと垣間見る体験を共有していただけたら幸いです。
あとがき
振り返り
長い長い記事を読んでいただきありがとうございました。4万五千字ぐらい。書くのも疲れますが、読むのも疲れますよね。ひょんなコトからLandscape with Rというトークをしたんですが、この題名は我ながら良いな〜と思っていました。神経科学者の端くれとしてはRそのものもイイんですが、Rを書いたり読んだりしている時の脳のハタラキとか気になってたまりませんね、ハァハァ。今年のアドベントカレンダーは、「はじめに」に書いた通りノイズの煮こごりを載せようと決めてから四苦八苦してきました。伝えたい内容を煮詰めて出来るだけ簡潔に短くまとめ、読み手の負荷を最小化する事で伝達効率を上げるべきだ。これは正論です。プログラミングの大原則と言っても良いでしょう。ところが何しろノイズの煮こごりなので、全編に渡って内容が「削ぎ落とされる側」なんですね。たまには削ぎ落とされるものをくっつけたままの肉付きの良い文章を書いてみたいなというやつです。ただ、そういうモノを言語化するというのは難しくて(慣れていなくて)、スパゲッティ記事と化した面は否めないですが、これに懲りず、今後も煮こごり製造機として精進していきたいと思います。
謝辞
前述の通り、2018年はTokyo.R1というRユーザーコミュニティで運営チームの1人として活動してきました。この文章はそのTokyo.Rの初心者セッションでトークした内容と、いただいた色々なご意見を踏まえながら書きました。実験的に色々と試させていただき、おかげ様で何をどう伝えるとどう伝わるのか(あるいは伝わらないのか)を実践的に学ぶことができました。この文章を読んで、懲りてないなぁとおっしゃられる方も多いかと思いますが..。この場を借りてTokyo.Rに参加していただいた方々、反響いただいた方々、また、運営チームの面々に感謝申し上げます(&今後ともよろしくお願いいたします)。
-
uriさんの紹介記事:r-wakalangへようこそ ↩
-
小一時間もワケの分からんヤツ(ワタシの事ですね)の話を理解しようとしてくれたので親切な方だったのでしょう。 ↩
-
超訳は、超適当に雰囲気だけ意訳した、の略語です。 ↩
-
このあまりにも有名なフレーズについて解説するためには、膨大な余白を必要とするため、この場では次のwikipediaの引用で済ませます。フェルマーの最終定理 - wikipedia ↩
-
Timeの記事より; He also spewed bile in the direction of FORTRAN (an “infantile disorder”), PL/1 (“fatal disease”) and COBOL (“criminal offense”). ↩
-
Intelligent designに対するカウンターですよ、念の為。 ↩
-
Ramachandran, V.S.; Hubbard, E.M., "Synaesthesia -- A window into perception, thought and language", J. of Consciousness Studies, (8) No. 12 2001, pp. 3-34(32) ↩ ↩2
-
このパートは『〈わたし〉はどこにあるのか: ガザニガ脳科学講義』マイケル・S. ガザニガ 紀伊國屋書店を多く参照しました。 ↩
-
review: P. Kay and W. Kempton, "What Is the Sapir‐Whorf Hypothesis?" American anthropologist, 1984 ↩
-
この発言自体の一次情報が取れませんでした。有名人が言ったとされている格言の類はこうした参照元不明なモノが多いですね。ここでは格言サイト?で出てきた一番原文っぽいものの表記をそのまま記載しました。Eskimoという呼称は元々は差別的呼称ではなかったらしいのですが後に蔑称としての性質を帯びた経緯があるそうで、またInuitという呼称もそもそも単一民族に対応しているわけではなく、本質的には現地の自称を尊重するべきです:cf. "Inuit or Eskimo: Which name to use?"。カナダ政府は特にカナダ圏のツンドラ地帯に住む先住民にはInuitの呼称を公的に用いています:1982年憲法法第35項。ちなみにこの条項はRIGHTS OF THE ABORIGINAL PEOPLES OF CANADAを定めたものですが、この条項が差別的待遇の撤廃を保証しているか(コレ自体が差別的条項ではないか)という点については現在も議論があります。いずれにせよ複雑な背景を持った用語なので記事本文の表記では蔑称としての誤解を避けるために「イヌイット」を用いました。 ↩
-
ところで「個人的には思います」という表現がありますが「思う」は常に個人的プロセスですね ↩
-
また面倒臭いことを言いますが、「動物の幼若個体」を指す時に、学術分野では英語ではヒトも動物もchildをあてますが、日本語では「仔」をあてます。例えば「動物の子育て」とは言いわずに「動物の仔育て」と言います。同じように「胎児」もヒトを指す用語で、動物では「胎仔」と表記します。これは慣例的な区別だと思いますが..。英語では区別せずにembrioですねっと言いたいところですがこれもややこしくて、fetusという別の単語もありこれは特にヒトの受胎9週以降について当てます。動物でも脊椎動物にはfetusを用いることはあるそうですばあまり見かけません。 ↩
-
言いたければこれをオブジェクト指向プログラミングと呼ぶこともできますが、「オブジェクト指向」というラベルを世界から切り出してくる妥当性も考えた方が良いと思います(バズワードあるある)。 ↩
-
atusyさんの解説記事:furrr パッケージで ggplot のリストの表示を高速化する ↩