RLinkにおけるR言語データの型

Rは,簡単ではあるが強力な型のシステムである.RとWolfram言語の間のインターフェースとして,RLinkはRの型とWolfram言語の式の間のマッピングを実装する.RLinkを効率的に使うためには,このマッピングについて詳しく理解することが重要である.

簡略化されたR言語オブジェクトモデル

以下のスキームは,Rの簡略化されたオブジェクトモデルをRLinkが使う方法で表したものである.

                |RCode
RObject     --> |RCoreObject + RAttributes
                |REnvironment

                |-- NULL                            
RCoreObject --> |-- RVEctor
                |-- RList
                |-- RFunction

RFunction    -->    |-- builtin
                |-- closure                
                
RVector        --> [(RNativeType|NA)..]

NA            --> 欠落要素(ベクトルのどの位置にあってもよい)

                |--    integer
                |--    double
RNativeType --> |--    complex
                |--    logical: TRUE|FALSE        
                |--    character (string)
                
RList        -->    {RObject..}

RAttributes    --> RList

このスキームから分かるように,このモデル内で,任意のR言語オブジェクトはRベクトル,Rリスト,R関数,RのNULLオブジェクトのいずれかとして表すことができる.また,R言語オブジェクトは属性を持つことができ,この属性自体はR言語リストに保存される.REnvironmentRCodeの型のオブジェクトもあり,これらはそれぞれR環境と一般的なR言語オブジェクトを表し,RLinkで特別のサポートを持たない.この2つの型は,表現(Wolfram言語側)のみに使われるという点で他の型とは異なり,一般にこれらの型のオブジェクトから正しくR言語オブジェクトを構築することはできない.

RLink におけるデータ表現の長い形式と短い形式

RLinkでは,R言語オブジェクトをWolfram言語式で表すときに,2つの異なってはいるが等しい方法を使う.1つは,可能であれば,Wolfram言語で同じようなオブジェクトを表す標準的な方法にできるだけ近い形式で表現する方法で,ほとんどの場合にはこの形式を使うことになる.もう一つは,RLinkの内部表現であり,これは通常より長く読みにくいものであるが,完全に曖昧さを欠き,Rとの交信に最適な形式である.このことを説明する最も近い例えとして,短縮形式はWolfram言語のInputFormのように動作するのに対し,より長い形式はWolfram言語のFullFormに似ていると言える(実際これはかなり近い類似性である).

下に説明するような特別の数件の場合を除き,2つの形式間のマッピングに曖昧性はなく,ToRForm(短い形式から長い形式)とFromRForm(長い形式から短い形式)という2つの関数で実現できる.短縮形式では,R言語オブジェクトは通常のWolfram言語頭部(List,Wolfram言語原子(「原子オブジェクト」を参照)等)の他に,RObject RAttributesRCodeREnvironmentRFunctionといういくつかの特別の頭部を持つことができる.後ろ3つの頭部は,長い形式で提示することもでき,実際これらはToRFormによって変換されることがない.頭部RObjectは,与えられたR言語オブジェクトの属性のためにデータを運ぶコンテナ(属性集合が非空である場合)であり,頭部RAttributesは,属性を保存するコンテナである.長い形式では,R言語オブジェクトはRVectorRListRNullという3つの追加の頭部,および上と同様にRAttributesによって表される.頭部RObjectは長い形式では使われない.

パッケージは使う前にロードしなければならない.

例として,整数の簡単なベクトルを短縮形式で表す.

長い形式では,整数行列は以下のように与えられる.

もう一つの例として,整数行列を短縮形式で表すことができる.

一方,長い形式は以下で与えられる.

ToRFormFromRFormの関数を使って,1つの形式からもう1つの形式に変換することができる.

ほとんどの場合,R言語オブジェクトの長い形式は必要ない.しかし,これが役に立つこともある.特に,RLinkで短縮形式の入力の解釈を確かめたいという場合がそうである.

RLinkの式の短縮形式と長い形式の例は,ToRFormFromRFormの関数のドキュメントを参照されたい.

型の自動検知

RLinkを通してRにデータを送る場合に,RLinkは送られるデータの型を自動的に検知しようとする.これは,このデータをRセッションでデータが保存されるR言語オブジェクトの型に正しくマップするために必要である.技術的に起るのは,入力がWolfram言語式に変換されて,前のセクションで説明されているように,RLinkで長い内部形式が与えられるということである(したがって,型の検知はToRFormの機能の一部である).その後,RLinkはこの内部形式で表されたデータをRセッションに送る.

型の検知は,次の規則に基づいて行われる.

1. StringIntegerRealComplexTrue|Falseという型のスカラー(自動要素)は,対応するRの型の一要素ベクトルであると解釈される.次の表は,Wolfram言語とRの基本的な型の対応を示す.

Integerinteger
Realdouble
Complexcomplex
Stringcharacter
True|Falselogical

Wolfram言語とRの型の対応(ベクトルの型)

2. Missing[]要素は,それ以外は有効なRベクトルを表すリスト(ネストしたリストの場合もある)内部で見付かるRの欠落要素NAであると解釈される.

3. 同じ基本の型(StringIntegerRealComplexTrue|Falseのいずれか)の要素のリストまたは正規配列(リストのリスト)は,Missing[]要素を含む場合も含めて,Rベクトルであると解釈される.これが多次元配列である場合には,結果のR言語オブジェクトの"dims"属性が加えられ,配列の次元を保存する.

4. その他の要素のリスト(さまざまな型の要素を含むリストや非アトミック要素のリストを含む)は,要素自体が有効なRLinkの解釈を持つ場合(つまり,この型の認識過程は再帰的に適用される場合)には,Rリストであると解釈される.

5. Wolfram言語のNullは,RのNULLオブジェクトとして解釈される(頭部RNullで表される).

6. 明示的なRの属性を持つデータは,RObject[data,RAttributes["name1":> value1,]]として入力されなければならない.このようなデータの型は,dataの型で決められる.属性のvalue1等の値は,それ自身有効なRLinkの解釈(RLinkがサポートする任意のR言語オブジェクトでよい)を持たなくてはならない.

7. RCodeREnvironmentRFunctionの頭部を持つ要素は決して変換されない(例外はそれに含まれる属性である).つまり,短い形式と長い形式が一致する.

ここで説明したスキームには曖昧性が残る.このことは重要なので,下の「型の検知の曖昧性」のセクションで説明する.

これらの規則で解釈できないWolfram言語式はどれも,RLinkの視点から見ると有効なR言語オブジェクトの表現であるとは言えず,RLinkと通してRに交信することはできない.このような式についてToRFormを呼び出そうとすると,エラー($Failedが返される)が起る.

ベクトル

Rベクトルは,Rにおいてコアとなるデータの型であり,同じ基本的な型の要素をまとめるものである. RLinkでサポートされる型は,integerdoublecomplexlogicalcharacterである.多次元配列もR言語ではベクトルによって表され,次元は特別な属性"dims"によって指定される.Wolfram言語側では,Rベクトルは通常のように,リスト(ネストしたリストの場合もある)として表される.

まず,パッケージをロードしてから,Rのランタイムをインストールする.

integerのベクトルは以下のように入力できる.

この内部形式である.

Rベクトルの長い形式は,常に頭部RVectorを持つ.この頭部内部の第1要素は,ベクトルの型を与える文字列であり,第2要素はデータの一次元リストであり,最後はベクトルに付属していることがある属性RAttributesのコンテナである.

ベクトルはMissing[]で表される欠落要素を含むことができる.

多次元配列も普通に入力することができる.

その場合,配列の次元は"dims"属性(Rでこのような配列がどのように扱われるかに対応する)に保存される.

1つ重要な違いとして注意しなければならないのは,Wolfram言語では配列を行単位で並べて(C言語でも配列は同じように保存される)保存するのに対し,Rでは列単位で並べて保存するという点である.配列がRに送られるとき,これは列単位で並べる形式に変換される.上の例では,配列の長い形式の内部のデータリストが,例えばFlattenを配列について呼び出すことによって得られるものについて並び替えられることに反映されている.配列がRからWolfram言語に送り返されるときに,これは行単位で並べる形式に変換し直される.このことによって,Wolfram言語とRにおいて一貫性を持って配列を使うことができる.これについては,REvaluateについてのドキュメントで詳しく説明されている.RベクトルがRLinkでどのように表されるかについての例は,RVectorのドキュメントを参照されたい.

リスト

Rリストは,さまざまな型のR言語オブジェクトの要素を含むこともできる,より一般的なコンテナである.Rリストの要素は,他のRリストを含む任意のR言語オブジェクトでよい.RLinkで使われる簡略化されたRのオブジェクトモデルのコンテキストで考える場合には,このことは,RリストがRベクトル,RのNULL 要素,他のRリスト,R関数の参照,R環境オブジェクト,および頭部RCodeを持つ式で表される他のR言語オブジェクトを含むことができることを示している.

まず,パッケージをロードしてRのランタイムをインストールする.

通常,RリストはWolfram言語のリストとして入力することができる.

Rリストはどれも,頭部RListを持つWolfram言語式として内部的にはRLinkによって表される.上の例で,長い形式は以下のようになる.

この例から分かるように,このリストの要素は,長さ1のRベクトルとして解釈される.

しかし,リストがRLinkによって違って解釈される重要な場合が1つある.先ほど見たように,要素がすべて同じ基本的な型のものである場合には,入力されたリストはベクトルであると解釈される.この解釈の曖昧性については,「型の検知の曖昧性」のセクションでもう少し詳しく説明する.RLinkがどのようにRリストを取り扱うかについて例は,RListのドキュメントを参照されたい.

Null

RLinkは,RのNULLオブジェクトを内部的にはWolfram言語式RNull[]として表す.

まず,パッケージをロードしてRのランタイムをインストールする.

Wolfram言語のNullRNull[]として解釈される.

これは両方向において真である.

Wolfram言語ユーザにとっては,RのNULLオブジェクトが,Wolfram言語におけるNullSequence[]を組み合せたものにある程度似た役割を果たすというと考えたほうが分かりやすいかもしれない.特に,Rリストの要素をNULLに設定することは,Wolfram言語におけるSequence[]と同じように,リストを効率的に縮小する.しかし,別の場合にはNULLはRで,Wolfram言語におけるNullと似た方法で使われる.

R言語オブジェクトの属性

R言語のオブジェクトは,1つあるいは複数の属性を持つことができる.属性とは鍵と値のペアであり,鍵は文字列(属性の名前)で,値は任意のR言語オブジェクトである.属性自体はRリストに保存され,与えられたオブジェクトにリンクされる.

属性はRで重要な役割を果たす.特に行列と多次元配列では,属性"dim"が与えられた配列の次元を保存する.任意のR言語オブジェクトでは,属性"class"(あれば)がこのオブジェクトがインスタンスとなるクラスについての情報を保存する.この両方の場合において,属性は動的に変更することができるため,かなりの柔軟性を持たせることができる.これはつまり,"dim"属性を操作するだけで,複雑な配列を簡単に入れ替えすることができ,与えられたオブジェクトのクラスをランタイムで変更する(これはほとんどのOO言語では不可能なことである)ことができることを意味する.

RLink は,与えられたR言語オブジェクトの属性用のコンテナとして頭部RAttributesを使う.属性自体は,規則の左辺の文字列が属性の名前で,右辺が値である遅延規則として入力される.入力が明示的な属性を持たない("dim"属性は配列の次元から推察することができるので,明示的に追加する必要はない)オブジェクトを表す場合には,RAttributesを使う必要はない.しかし内部では,常に属性が使われる.

まず,パッケージをロードしてRのランタイムをインストールする.

例えば,簡単なベクトルは,属性の空集合を持つ.

R言語オブジェクトに明示的な属性を提供する必要がある場合には,オブジェクトは短縮形式のデータdataで表され,RObject頭部(コンテナ)を使い,これでdataの周りを囲み,第2要素として属性を持つRAttributesコンテナを加えなければならない.

例えば,別の整数リストを値として持つ属性"myAttr"を加えたいとする.

RObjectは,R言語オブジェクトの短縮形式に使われるコンテナであることに注意する.RObjectが長い形式には現れることはない.これは,RLinkによって取り扱われるデータはリスト,ベクトル,NULLのいずれかであり,長い形式ではこれらはそれぞれ頭部RListRVectorRNullで表されるからである.

以下は,オブジェクトの長い形式の例である.

この例から,属性の値そのものが長い形式に変換されることが分かる.もちろん,逆の変換を行うと,もとのオブジェクトが短縮形式で表示される.

希望するなら,R言語と交信する(REvaluateRSet等の関数を通して)際に,常に長い形式を使うこともできる.その場合には,RObject(R言語オブジェクトの短縮形式の表記のみに使われる非システムの頭部)を必要とすることはない.

もう少しおもしろい例として,与えられた整数ベクトルをRの表に変換し,この表をWolframシステムセッションに返すことを行ってみよう.

ランダムな整数のリスト(Rベクトル)を生成する.

これをRに送り,Rのワークスペースの変数rndに割り当てる.

要素の頻度を計算し,tableオブジェクト(RLink におけるこれの表記)を返す.

属性のリストは非空であり,RObjectは結果の短縮表記に使われることが分かる.また,属性の値の1つがそれ自身,属性の非空集合を持つR言語オブジェクトであり,RObjectの頭部で表される.

環境

R環境は,Rでは別個のデータ型である.これは,カプセル化の背後にある基本的な構造として使われる. R関数はすべて特定の環境で定義され,その環境で定義された変数にアクセスできる.RLinkでは現在環境については非常に限られたサポートしか提供していない.基本的に,R言語オブジェクトの一部として明示的に現れる環境はすべて,RLinkではREnvironment[]として表される.つまり,非大域的な環境についての情報はWolfram言語へインポートされる際に失われるということである.したがって,非大域的な環境を参照するR言語オブジェクトは,Wolfram言語からRにエキスポートされなおすことができない.しかしクロージャは関数参照の構造を通して完全にサポートされている.

まず,パッケージをロードしてRのランタイムをインストールする.

現行の環境(大域環境)を返す.

これは,"environment"の型を持つ.

クロージャを作成する.

クロージャの環境(大域的なものにはなりえない)についてのクエリを行う.

したがって,この環境についての情報は失われてしまった.

まとめると,環境はRにおける特別のデータ型であり,これは主にRの内部で使われる.しかし,これが特定のR言語オブジェクトによって明示的に参照されることもある.Wolfram言語でこれらのオブジェクトをインポートできるようにするために,環境オブジェクトを一般的に表すのに使われる頭部REnvironmentがある.これは環境同士の区別を付けないので,非大域的なR環境を参照するオブジェクトを正しくRにエキスポートしなおすことはできない.この規則の例外は,クロージャであり,これはRLinkの別の構造によって取り扱われる.RLinkにおける環境についての詳細は,REnvironmentのページを参照されたい.

文字列形式のR言語コード

RLinkは,Rのコアとなるデータ型すべてをサポートするわけではない.しかし,RLinkがサポートしないデータ型のほとんどは,通常ユーザによって使われるものではなく(あるいは,特別な状況のみで使われるものであり),R自体のために必要であることが多い.いずれにしても,サポートされないデータ型を要素として持つオブジェクトを含むかどうかにかかわらず,Wolfram言語の任意のR言語オブジェクトにインポートできるようにしたい.これを可能にするために,RLinkは次のような方法を取る.サポートされない型のオブジェクトを見付けた場合には,RLinkがそのオブジェクトの文字列コード表現を構築し,この文字列がR側で解析,評価(R関数のparseeval )されたときに,もとのR言語オブジェクトが再構築される.そのような文字列を構築するのには,R関数のdeparseが使われる.この方法ですべてのR言語オブジェクトが正しく再構築されるわけではない(環境は主な例外の1つで,これはdeparseすることはできない)が,ほとんどのオブジェクトはされる.結果のdeparseされたコードの文字列は,RCodeラッパーに囲まれた形でWolfram言語に返される.

この方法が使われた例の1つに,関数参照を作成する場合がある.

まず,パッケージをロードしてRのランタイムをインストールする.

例えば,Rの組込み関数rank(これはR言語のトップレベルのコードで部分的に実装される)では,以下のような参照を得ることができる.

この参照のFullFormを見る.

RCodeで囲まれた因子のコードは,deparseの方法で得られたものであることが分かる.このコードを以下のようなもう少し見やすい形で抽出することができる.

原則として,このコードを使ってRのワークスペースで関数を手作業で定義することが可能である.

ここで気を付けなければならないことは,RCode[code]で表されるR言語オブジェクトがRにエキスポートされなおしたときに,必ずしももとのR言語オブジェクトとまったく同じものになるとは限らない(ほとんどの場合はそうなるが)ということである.このようなオブジェクトについての詳細は,RCodeのページを参照されたい.

関数の参照

関数の参照は,R関数(組込み関数とR言語で書かれた関数の両方)を表すための RLink の構造であり,Wolfram言語内からWolfram言語の引数として呼び出すことができるものである.正式には,RLink では"builtin""closure"の型を持つR言語オブジェクトを表す.どちらの型も,Rから取り出したり,Rに送信したり,他のR言語オブジェクトの一部として使われたりできるという点で,RLinkで完全にサポートされるものである.関数の参照については,別のチュートリアル「関数」に詳しく説明されているので,ここでは2つ例を挙げるに留める.

まず,パッケージをロードしてRのランタイムをインストールする.

簡単なユーザ定義の関数についての関数の参照を作成する.

これを使用する.

同じような参照をREvaluateを使って作成することもできる.

これを使用すると以下のようになる.

ここで作成した2つの参照は,まったく同じというわけではない.REvaluateに基づくメソッドのほうがより一般的(例えば,REvaluateは他の関数によって結果として返される関数であるクロージャを返すことができるのに対し,RFunctionベースのメソッドは通常クロージャを作成するのに使ってはならない)であるが,RFunctionベースのメソッドのほうが望ましい.このことについては,「関数」で詳しく説明されている.

組込みの(プリミティブ)R関数についても関数参照を作成することができる.

これを使用する.

すべての関数参照は,頭部RFunctionを持つ.

また,これらはどれもToRFormFromRFormのアクションによって変化するものではない.

関数参照についての詳細は,RFunctionのページと「関数」を参照されたい.

その他の(コアではない)データの型

Rは,強力な型のシステムを持ち,上で説明された型は,コアとなるRの型の一部分を表すにすぎない.factorsdata framesといったここで取り扱われていない他の型についてはどうであろうか.これらの型(およびその他のコアではない型)は,コアとなる型に準ずる型(例えば,因子は整数ベクトルであり,データフレームはリストである)であるので,RLinkはこれらのオブジェクトを取り扱うことができる.しかし,常にもっとも一般的な形のものを取り扱うというのではうまくいかない場合もある.この問題に対処するために,RLinkには型を拡張するシステムがあり,これについては,下の「型の検知の曖昧性」で説明する.またRLinkでは,因子とデータフレームのデータ型に対して必要最小限のサポートを提供し,これは型を拡張するシステムを使って実装される.その他のデータ型については,このシステムを使ってユーザはコアとなるRLinkのコードを変えることなく,サポートを加えることができる.

型の検知の曖昧性と,データの解釈を強制する方法

ベクトルとリスト

RLinkが入力データを解釈する方法の中で唯一の最重要な曖昧性は,同じ基本的な型の要素を含むリスト(ネストしたリストの場合もある)を提供する場合である.これは,原理上はRベクトルとRリストの両方として解釈することが可能である.この場合にRLinkはデフォルトではRベクトルの解釈のほうを選ぶ.このことはすでに「ベクトル」のセクションで例に見た通りである.

しかし場合によっては,このようなオブジェクトがRリストであるとの解釈と強制したい場合もある.RLinkユーザガイドの「性能」のセクションで説明されるように,RLinkではRベクトルと比べてRリストを扱うほうがずっと非効率であるので,この強制を行う場合には十分考えた上で行うべきである.これを実際に行いたい場合には,明示的にRListの頭部を使い,データをRList[data,RAttributes[]]としてラップする必要がある.

まず,パッケージをロードしてRのランタイムをインストールする.

整数のリストがあるとする.

これはデフォルトではベクトルとして解釈される.

Rリストの解釈を強制する.

これで入力リストは,要素が一要素のベクトル(Rでは基本的な型のスカラーを持たないので,これらは一要素のベクトルとして扱われる)であるRリストとして解釈されるようになった.

同じような曖昧性は,多次元リストにも見られる.

これはデフォルトでは,1つのシングルトン次元を持つ多次元配列として解釈される.

同じコンストラクトを使って,リスト解釈と強制することができる.

このような入力では,ToRFormFromRFormを組み合せて使っても,他のほとんどの場合とは違って,入力とまったく同じ結果を返すことはない.

Rから受け取る式は常に同じであるので,データをRに送る場合にだけ気を付ける必要がある.

スカラー

気を付けたほうがよいもう一つの曖昧性に,Wolfram言語側で入力データとして使われた場合に,基本的な型のスカラーが常に,対応するRベクトルの型の一要素ベクトルであると解釈されることがある.

まず,パッケージをロードしてRのランタイムをインストールする.

以下の例を考える.

これは,このようなデータのRでの解釈とは一貫しているが,Wolfram言語に返されたときに,このようなスカラーが余分なListに囲まれた形になってしまうという副次的な結果が起る.

RLinkを使う場合には,このことを心に留めておくべきである.

自分のデータ型を定義することによって,RLink の型のシステムを拡張する

RLinkの型のシステムは,ユーザが拡張できるように設定されている.R自体は非常に拡張しやすい言語およびシステムであるので,このことは重要であり,コアとなる型だけをサポートするのでは,拡張されたRのデータ型の多くを扱うのには都合が悪いということがあるかもしれない.このセクションでは,コアとなる型のシステムを新しいデータ型で拡張する方法について説明する.

このセクションの説明は,残りのRLinkドキュメントと比べると,より技術的で,あまり格式ばったものではない.型のシステムの拡張は一般に,RLinkを単に使うよりも高度なタスクであり,型のシステムを拡張しようと考えるユーザはある(純に技術的な)意味で,RLink自体の開発に深くかかわることになるので,このセクションは,より上級のユーザに向けて書かれている.

例題:RLink にすでに存在する型の拡張- データフレームと因子

エンドユーザの便宜を図る以外に,拡張システムは RLink 自体がfactorsdata frames等のRのデータ型を実装するのに使うものである.因子とデータフレームはそれぞれ実際には整数ベクトルとリストであるので,どちらもRLinkがサポートするコアのデータ型である必要はない.しかし,RLinkが通常提供するRObject頭部に基づくコアのR言語オブジェクト表現は,特にそれらの型に対して特別の関数を定義あるいは多重定義したいという場合には,不都合であることが多い.

まず,パッケージをロードしてRのランタイムをインストールする.

今度は,これがどのように動作するかを見る.簡単なデータフレームを構築する.

RDataFrameRNamesRDataRFactorRFactorLevelsの頭部は,コアとなるRLink APIには含まれていない.これらは,データフレームと因子のAPIを表す頭部であり,RLink の型の拡張システムによって実装されたものである.これらはRLink`DataTypes`Base`RLink`DataTypes`Common`のコンテキストの中に存在する.

そして,これらはそれぞれRLinkプロジェクト内の/Kernel/DataTypes/Base.m/Kernel/DataTypes/Common.mパッケージで定義される.

上のデータフレームのコアのRLink表現は,簡単に得ることができる.一時的にデータフレームと因子の型を非登録にすればよい.

同じコードを評価することによって,コアのRLink表現を得る.

余分な頭部に基づく表現を持つことには,いくつかの利点がある.1つは,型が特定の頭部に関連付けられることになるので,より強力な型になるということである.もう1つの利点は,数多くの助けとなる関数を定義,多重定義することができるということである.常にRObjectを使わなければならないと,そのような定義により複雑なパターンが必要となり,マッチして最初から正しいものになるのに時間がかかる.しかも,システム関数の多重定義には,RObject (UpValues)に規則を加える必要のある場合があり,これは望ましいことではなく,エラーを起しやすい.

このことを示すために,まずRDataTypeUnregisterで先ほど無効にした定義を再び有効にする.一番簡単な方法は,RDataTypeDefinitionsReload関数を呼び出すことで,この関数は,RLinkが知っているすべての拡張されたデータ型を動的にリロードする.

これで何らかの機能を示すことができる.まず,データフレームの一部を抽出することができる.例えば,以下でデータフレームからデータを抽出する.

名前を抽出する.

行の名前を抽出する.

データに含まれる因子を取り出すことができる.

因子からもデータを抽出することができる.

RGetData自体は何の規則も持たないことに注意する.

規則は,特定の方(ここではRDataFrameRFactor here))を表す頭部に付随するものである.これは,他の型がどのように頭部を多重定義するかを考慮せずに,異なるデータ型が同じ一般的な頭部を安全に多重定義することができることを意味する.もしもRObjectに基づくコアの表現のみを使った場合には,これは不可能である.その場合には,多重定義した関数あるいはRObjectの頭部自体がさまざまなデータ型から規則を集めなくてはならない.これはあまり大きな問題ではないように見えるかもしれないが,型のシステムが真の意味で拡張可能であるかどうかはこれにかかっている.拡張性の必要条件は,2人のユーザがそれぞれ2つの異なる新しい型を使ってシステムを拡張することができ,お互いの実装について確認し合わなくても,これらの新しい型が確実に連携して動作するということであるからである.

選択子以外でも,データ変換を実装することができる.例えば,データフレームの中で因子を整数ベクトルに変換する.

また,与えられたデータ型で表示するためにさまざまな関数を定義あるいは多重定義することができる.

新しく定義されたデータ型は,すべての高レベル関数(RSetRFunctionREvaluateで返される)で使えるという意味で,RLinkの型のシステムにおける第1級メンバーである.例えば,データフレームをR言語ワークスペースの別の変数に割り当てることができる.

これでどのR言語コードと一緒にでも使うことができる.例えば,20歳を超える年齢の人の記録だけをフィルターするのに使うことができる.

また,関数の引数として渡すこともできる.

しかし何よりも,システムの残りの部分について考慮しなくても,自分のデータ型に機能を加えることができるのである.

簡単なデータ型をインタラクティブに定義する

ここでは,新しいデータ型を定義し,RLinkにそのことを知らせる方法について見てみよう.2つの方法でこれを行うことができる.まず,関連コードを実行して,新しいデータ型をインタラクティブに(フロントエンドで)登録する方法がある.この場合,定義は現行のRLinkセッションのみでRLinkが使用できる.また,これらの定義を.m ファイル(パッケージ)に置き,RLinkにそれがどこにあるかを知らせる方法もある.その場合は,これらの定義はRLinkが起動する際にロードされ(InstallR),RDataTypeDefinitionsReload関数を使えばいつでもリロードすることができる.

まず最初に,インタラクティブにデータ型を登録する方法,そしてこれらの定義を持続させる方法について見てみる.データ型を登録するには,RDataTypeRegister関数を呼び出す必要がある.例として,簡単な新しい型について定義を作成する.型の識別は"class"属性で伝えられ,この属性はオブジェクトを1つあるいは別のクラスのインスタンスとして識別するのにRで使われる.

まず,パッケージをロードしてRのランタイムをインストールする.

ベクトル等のコアとなるRのデータ型を囲む非常に簡単なデータ型(ラッパー)を登録する.

RDataTypeRegisterには5つの引数があることに注意する.1つ目の引数は型の名前(厳密には必須ではないが,文字列であることが一番望ましい),2つ目はこの型のインスタンスで識別される「高レベルパターン」,3つ目はそのような高レベル表現を低レベルのRObjectベースの表現に変換するための変換規則,4つ目はRObjectベースの表現にマッチするパターン,そして最後はRObjectに基づく表現を高レベル表現に変換する「逆変換規則」である.The RInstanceOf f関数は,/Kernel/DataTypes のサブフォルダにあるRDataTypeTools.m パッケージで定義されるヘルパー関数であり,式が与えられた型のインスタンスを"class" 属性の値に基づいて表すかどうかをテストする.自分のデータ型を定義するのに便利なヘルパー関数が他にもそのパッケージで定義されている.

規則(および送信の構造)が"class"属性の値に連結している必要はないが,この方法を使うと異なるデータ型間で規則の衝突が起る可能性を最小化し,Rの関数送信構造に対応するため,この方法を使うことを強くお勧めする.

ToRFormを使って,この定義が有効になったことを確かめることができる.

定義したばかりのカスタムのデータコンテナを使って,データをRに送信できる.

Rに戻されたときに,RDataTypeRegisterで与えられる逆変換規則に従って,結果が自動的に変換される.

これは,型(Rにおけるクラス)を変えないもとのオブジェクトを使った操作を通して得られる派生的なR言語オブジェクトにも使えることが分かる.

RDataTypeRegisteredQを使って型が現在登録されているかどうかをテストすることができる.

このデータ型を非登録にする.

これで,RObjectに基づく通常の表現が得られる.

新しい型は動的に登録したり非登録にしたりできるので,自分のデータ型をインタラクティブに開発することができる.新しい定義は,古いものを非登録にしなければ登録することができない.

持続的なデータ型の定義を作成する

このセクションでは,RLinkで登録した定義を,RLinkが自動的に見付けられるように,そしてRLinkセッションのたびに手作業で実行しなくてもいいように,持続的なものにする方法について見ていく.これは,定義を.mファイル(パッケージ)に保存し,RLinkにその場所を知らせることによって可能である.

上と同じ例を使う.まず,型の定義を含むファイルを作成しなければならない.

パッケージをロードしてRのランタイムをインストールする.

以下では,新しいデータ型のサンプル定義を保存する一時的なディレクトリを作成する.

これは,RDataTypeRegister関数の参照ページで使われたサンプルのデータ型のコードを文字列にしたもので,パッケージ(ネームスペース)でラップされている.

これをエキスポートして,この定義を持つファイルを作成する.

新しいファイルを含む,データ型の定義をリロードすることができるようになった.

オプション"AddToRDataTypePath"を使って,型の定義を含むファイルがあるディレクトリのリストを RLink の検索パスに加える.

ToRFormを使って,この定義が有効になったことを確かめることができる.

定義が完全に有効であることを確かめるために,前のセクションで行ったテストと同じようなテストをすべて行うこともできる.前のセクションの例とは違って,MyNewTypeの頭部は,Global`コンテキストではなく,特定のコンテキスト,つまりRLink`DataTypes`myNewType`に属するようになったことに注意する.一般に,新しい型を説明する頭部に割り当てられているコンテキストは,ユーザが決めるものであることはもちろんである.BeginPackage-EndPackageを省略することすらも可能である.その場合には,これらの頭部に割り当てられるコンテキストは,現行の作業コンテキスト(通常Global`)である.

もちろん,1つのパッケージ(コンテキスト)に複数の型の定義を置くこともでき,これは理にかなったことである場合が多い.特に何らかの形で複数のデータ型が関連している場合や,複数のデータ型が共通の機能を使う場合(その場合には定義をパッケージでプライベートにすることができる)がそうである.

InstallR"AddToRDataTypePath"オプションも取る.このため,このオプションをInstallR(RLinkセッションの最初に)渡すことによって定義がロードされ,RDataTypeDefinitionsReloadを明示的に呼び出す必要がなくなる.また,RDataTypeDefinitionsReloadは型の定義を動的にリロードし,これはファイルに保存された外部の型の定義を開発したりデバッグしたりするのに非常に便利であることもある.しかし,RDataTypeDefinitionsReloadを呼び出した後,インタラクティブに登録された定義はすべて削除されるので注意が必要である.ディスク(ファイル)に持続されている定義だけがロードできるのである.

その他の例

/Kernel/DataTypes/Base.m.にあるfactordata frameのデータ型の実装についての説明ですでに2つの例を挙げた.現在これらの実装には非常に基本的な機能しか含まれていないが,それでもこれがどのように行われるかを説明するものである.例えば,以下は因子の現行の実装である.詳細については,上記のパッケージを参照されたい.

partWithMissing[expr_, inds_List] :=
With[{posNA = Position[inds, Missing[]]},
MapAt[Missing[] &, Part[expr, MapAt[1 &, inds, posNA]], posNA]];




ClearAll[RFactor];
RFactorQ[_RFactor] := True;
RFactorQ[r_RObject] := RInstanceOf["factor"][r];
RFactorQ[_] := False;

RFactor /:
    RGetFactorLevels[ RFactor[_List, RFactorLevels[levs__], a : _ : None]] := {levs};

RFactor /: RGetData[ RFactor[p_List, __]] := p;

RFactor /:
    RGetAttributes[RFactor[_List, RFactorLevels[__], a : (_RAttributes | None) : None]] :=
        RGetAllAttributes[a];
        

Clear[RFactorToVector];
RFactorToVector[f_RFactor] :=
With[{data = partWithMissing[RGetFactorLevels[f], RGetData[f]]},
FromRForm @ ToRForm @
RObject[data, RRemoveAttributes[RAttributes @@ RGetAttributes[f], {"class" , "levels"}]]
];

RFactorToVector[_] = $Failed;


(*             Register the type             *)

RDataTypeRegister["factor",
    
RFactor[_List, RFactorLevels[__], a : (_RAttributes | None) : None],

RFactor[p_List, RFactorLevels[levs__], a : (_RAttributes | None) : None] :>
RObject[p, RAddAttributes[a, {"levels" :> {levs}, "class" :> "factor"}]],

_RObject ? RFactorQ,

RObject[p_List, a_RAttributes] ? RFactorQ :>
RFactor[p,
    RFactorLevels @@ RExtractAttribute[a, "levels"],
    RRemoveAttributesComplete[a, {"levels", "class"}]
]
]