座標の引数(ベクトル、matrix、bind、list)
points(x,y) は、points(1,2)のようにも、points(c(1,2),c(3,4)) のようにも記述できます。
引数のx,yは、それぞれ、ベクトルでも、行列でも、リストでも良いように出来ています。同じように関数を作りたいと考えました。
1. Rの配列、リスト
C++やC#のようなプログラミング言語では、型を作って、その配列を扱うことができます。しかし、Rでは配列として扱えるのは基本的な型だけのようです。逆に、C++やC# で型を作っても組み込みの演算子は作用しませんが、Rの演算子はベクトルを演算するように出来ています。
- > a <- as.numeric(1); b <- as.integer(2); c <- as.double(3)
- > d <- as.complex(4); e <- as.raw(5)
- > f <- as.logical(6); g <- as.character(7)
- > sprintf("typeof() a:%s b:%s c:%s d:%s e:%s f:%s g:%s",
- + typeof(a),typeof(b),typeof(c),typeof(d),typeof(e),typeof(f),typeof(g))
- [1] "typeof() a:double b:integer c:double d:complex e:raw f:logical g:character"
- > sprintf("class() a:%s b:%s c:%s d:%s e:%s f:%s g:%s",
- + class(a),class(b),class(c),class(d),class(e),class(f),class(g))
- [1] "class() a:numeric b:integer c:numeric d:complex e:raw f:logical g:character"
- > sprintf("mode() a:%s b:%s c:%s d:%s e:%s f:%s g:%s",
- + mode(a),mode(b),mode(c),mode(d),mode(e),mode(f),mode(g))
- [1] "mode() a:numeric b:numeric c:numeric d:complex e:raw f:logical g:character"
|
配列として扱うとは、array[i] のようにインデクスで参照する構文が使えることを考えています。array()は、基本的な型の配列を作ります。基本的な型のオブジェクト以外は、as.vector()されるとあるので、array()が作る配列の要素はベクトルだと考えられ、ベクトルの要素は基本の型です。
ここではmode()が表す数値型(numeric)を主に考えます。
Arrayの訳語が「配列」だと考えて、インデクスで参照する構文で扱えるものの総称に使います。
Rのmatrix()が作るオブジェクトは配列で、他の方法で作成したベクトルや配列と共に、行列計算の演算子や関数の入力になります。行列は数学関数などの説明で転置行列のように使われているようです。
Rにある配列を作る関数は、以下のようです。
関数 | 説明 |
array | Multi-way Arrays |
c | Combine Values into a Vector or List |
cbind | Combine R Objects by Columns |
rbind | Combine R Objects by Rows |
matrix | Matrices |
vector | Vectors - Creation, Coercion, etc |
character |
- > character(length=10)
- [1] "" "" "" "" "" "" "" "" "" ""
- > logical(length=10)
- [1] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
- > numeric(length=10)
- [1] 0 0 0 0 0 0 0 0 0 0
|
|
logical |
numeric |
ここでの、ベクトル、配列、行列には、単に同じ型のものが並んでいてインデクスで要素にアクセスすると言う以外の意味はありません。c(1,"a") のように書けば、最初の要素の型も character になって格納され、同じ型のものが並ぶことになります。
我流に解釈すれば、Rでは演算子が作用する対象はベクトルで、配列も準じたものなのです。ベクトルは主に基本型の列で、配列はベクトルをバインドしたもので、矩形に並びます。
list() や data.frame() もベクトルをバインドする機能だと思います。バインドするベクトルの型が異なっていても良い点で上述の配列とは異なっています。また、上述の配列と data.frame は矩形で、行、列の長さは、それぞれ全て同じですが、リストの要素は、それぞれ独立したベクトルで個別の長さです。
- > vector1 <- c(1,2,3)
- > vector2 <- c("4","5","6")
- > (u <- data.frame(vector1,vector2))
- vector1 vector2
- 1 1 4
- 2 2 5
- 3 3 6
- > u[,1];u[,2]
- [1] 1 2 3
- [1] "4" "5" "6"
- > vector1 <- c(1,2)
- > vector2 <- c("4","5","6")
- > data.frame(vector1,vector2)
- data.frame(vector1, vector2) でエラー:
- 引数に異なる列数のデータフレームが含まれています: 2, 3
- > list(vector1,vector2)
- [[1]]
- [1] 1 2
- [[2]]
- [1] "4" "5" "6"
|
2. vector について
Rの演算子が作用する対象がベクトルなのだと解釈すると、ベクトルの要素は同じ型で一律に処理できることを前提にしていると考えられます。
入れ物と中身と考えると、リストがコンテナで、中身はベクトルです。list() や data.frame() の行ベクトルか列ベクトルの一方が元のベクトルで、他方は要素ごとに型が異なる可能性があることになります。
まず、行と列を確認しておきます。
- > (u <- matrix(1:6, nrow=2,ncol=3))
- [,1] [,2] [,3]
- [1,] 1 3 5
- [2,] 2 4 6
- > u[1,]
- [1] 1 3 5
- > u[,1]
- [1] 1 2
|
2行、3列の matrix で確認すると、[1,] が水平方向の3つの値を示し、行ベクトルです。[,1]は垂直方向の2つの値を示し、列ベクトルです。list や data.frame も同様に[行、列]とインデクスすると考えます。
- > vector1 <- c(1,2)
- > vector2 <- c("4","5","6")
- > u<-list(vector1,vector2)
- > str(u[1])
- List of 1
- $ : num [1:2] 1 2
- > str(u[[1]])
- num [1:2] 1 2
- > (vector <- u[[1]])
- [1] 1 2
|
試してみると、list は2次元の配列としてアクセスできないようです。u[1] は最初の要素を list で返し、u[[1]] が元のベクトルを返します。
data.frame の場合は、ベクトルは「列」として格納されています。「行」を要求すると data.frame 型で返されます。
- > x <- c(1,2,3)
- > y <- c("4","5","6")
- > (u <- data.frame(x,y))
- x y
- 1 1 4
- 2 2 5
- 3 3 6
- > str(u[,1])
- num [1:3] 1 2 3
- > str(u[,2])
- chr [1:3] "4" "5" "6"
- > str(u[1,])
- 'data.frame': 1 obs. of 2 variables:
- $ x: num 1
- $ y: chr "4"
|
3. 配列のデータの並び
t <- (0:360)*(pi/180)
x <- cos(t); y <- sin(t)
plot(x,y,t=”l”)
と書けば円を描けます。
- > m <- cbind(x,y)
- > str(m)
- num [1:361, 1:2] 1 1 0.999 0.999 0.998 ...
- - attr(*, "dimnames")=List of 2
- ..$ : NULL
- ..$ : chr [1:2] "x" "y"
- > plot(m,t="l")
- > n <- rbind(x,y)
- > str(n)
- num [1:2, 1:361] 1 0 0.9998 0.0175 0.9994 ...
- - attr(*, "dimnames")=List of 2
- ..$ : chr [1:2] "x" "y"
- ..$ : NULL
- > plot(n,t="l")
|
cbind(x,y) は、xがm[,1]です。rbindだとxはn[1,]です。mをplotすると円、nをplotすると直線が描かれます。
- > u <- matrix(c(x,y),ncol=2)
- > str(u)
- num [1:361, 1:2] 1 1 0.999 0.999 0.998 ...
- > plot(u,t="l")
- > v <- matrix(c(x,y),nrow=2)
- > str(v,t="l")
- num [1:2, 1:361] 1 1 0.999 0.999 0.998 ...
- > plot(v,t="l")
|
matrix の場合、uをplotすると円、vをplotすると直線が描かれます。
plot などのグラフ描画関数は、xy座標を配列で受け取りますが、[,1] が x、[,2] が y と決まっています。
配列は「列」をバインドして出来ていると考えるのが普通のようです。
しかし、配列の要素は全て同じ型なのが普通と考えられるので、通常は、列ベクトルと行ベクトルは対等に扱われるものと思います。以下は、ソートの例です。
- > x <- c(1,3,2,4)
- > y <- c("a","c","b","d")
- > u <- cbind(x,y)
- > str(u)
- chr [1:4, 1:2] "1" "3" "2" "4" "a" "c" "b" "d"
- - attr(*, "dimnames")=List of 2
- ..$ : NULL
- ..$ : chr [1:2] "x" "y"
- > u[order(u[,1]),]
- x y
- [1,] "1" "a"
- [2,] "2" "b"
- [3,] "3" "c"
- [4,] "4" "d"
- > v <- rbind(x,y)
- > str(v)
- chr [1:2, 1:4] "1" "a" "3" "c" "2" "b" "4" "d"
- - attr(*, "dimnames")=List of 2
- ..$ : chr [1:2] "x" "y"
- ..$ : NULL
- > v[,order(v[1,])]
- [,1] [,2] [,3] [,4]
- x "1" "2" "3" "4"
- y "a" "b" "c" "d"
|
1列目、あるいは1行目で配列をソートしています。
多くの場合、配列はランダムにアクセスするので、メモリ上での連続性を意識する必要はなく、どちらの並びでも間違えなければ良いことです。特別な理由がなければ「普通」にしておけば良さそうです。
4. matrixのbyrow
数列 c(1,2,3, 4,5,6) があって、意味的に x <- c(1,2,3) 、y <- c(4,5,6) と分けられるとします。
- > matrix(c(1, 2, 3, 4, 5, 6), nrow = 2)
- [,1] [,2] [,3]
- [1,] 1 3 5
- [2,] 2 4 6
- > matrix(c(1, 2, 3, 4, 5, 6), nrow = 3)
- [,1] [,2]
- [1,] 1 4
- [2,] 2 5
- [3,] 3 6
- > matrix(c(1, 2, 3, 4, 5, 6), ncol = 2)
- [,1] [,2]
- [1,] 1 4
- [2,] 2 5
- [3,] 3 6
- > matrix(c(1, 2, 3, 4, 5, 6), ncol = 3)
- [,1] [,2] [,3]
- [1,] 1 3 5
- [2,] 2 4 6
|
matrix()の nrow、ncol では、x、y が 行になるような配列(3行2列)しか作れません。
byrow=TRUE とすると、x、y が 列になるような配列(2行3列)しか作れません。
- > matrix(c(1, 2, 3, 4, 5, 6), nrow = 2, byrow=TRUE)
- [,1] [,2] [,3]
- [1,] 1 2 3
- [2,] 4 5 6
- > matrix(c(1, 2, 3, 4, 5, 6), nrow = 3, byrow=TRUE)
- [,1] [,2]
- [1,] 1 2
- [2,] 3 4
- [3,] 5 6
- > matrix(c(1, 2, 3, 4, 5, 6), ncol = 2, byrow=TRUE)
- [,1] [,2]
- [1,] 1 2
- [2,] 3 4
- [3,] 5 6
- > matrix(c(1, 2, 3, 4, 5, 6), ncol = 3, byrow=TRUE)
- [,1] [,2] [,3]
- [1,] 1 2 3
- [2,] 4 5 6
|
plot などのグラフ描画関数は、列ベクトルが、それぞれ x、y なので、2つのベクトル x、y を c(x,y) と書いて matrix を作るとすると byrow=TRUE は必須です。
5. 座標を表す配列引数
plot()やpoints()などの関数は、引数が配列である場合、[,1]をx、[,2]をyと見なすと考えられます。
配列は基本的な型で作られ、(x,y)のような組の配列を作るようには考えられていないようです。
[,1]はxの値のベクトルで、[,2]はyの値のベクトルです。
座標を表す配列の引数は、xのベクトルとyのベクトルをバインドしたものです。
- x<-0; y<-0
- plot(x,y,col="white",xlim=c(-5,5),ylim=c(-5,5))
- points(0,0,pch=16,col="red")
- points(c(1,2,3),c(3,2,1),pch=16,col="blue")
- u <- cbind(c(1,2,3),c(-3,-2,-1))
- points(u,pch=16,col="green")
- u <- matrix(c(-3,-2,-1, 1,2,3),ncol=2)
- points(u,pch=16,col="magenta")
- points(list(x=c(-1,-2,-3),y=c(1,2,3))) # OK
- points(list(y=c(-1,-2,-3),x=c(1,2,3))) # x,yを逆に書いてもOK
|
この例は、意図通り描画されます。
以下は、意図通りには描かれません。
- u <- rbind(c(1,2,3),c(-3,-2,-1))
- #(u[1,1]=1,u[1,2]=2),(u[2,1]=-3,u[2,2]=-2)の
- points(u,pch=16,col="green") # 2点のみ描画
- u <- matrix(c(-3,-2,-1, 1,2,3),nrow=2,byrow=TRUE)
- points(u,pch=16,col="green") # 2点のみ描画
|
上手くいかないのは、行と列が逆になっているからです。plot()やpoints()は、作成した側の意図とは無関係に決まった順でベクトルを参照します。
しかし、list()の場合は、ラベルには意味があります。
6. リストとラベル
リストの場合は、要素のベクトルの名前x、yが有効に見えます。
- > points(list(a=c(-1,-2,-3),b=c(1,2,3)))
- xy.coords(x, y) でエラー:
- 'x' はリストですが,成分 'x' と 'y' を持ちません
|
確かに、リストのラベルはplot()やpoints()関数で認識されるラベルです。
配列にも行名、列名を付けられます。
|
- > x<-0; y<-0
- > plot(x,y,col="white",xlim=c(-5,5),ylim=c(-5,5))
- > x<-c(-1,-2,-3); y<-c(1,2,3)
- > u <- cbind(x,y)
- > u
- x y
- [1,] -1 1
- [2,] -2 2
- [3,] -3 3
- > points(u,pch=16,col="red")
- > u <- cbind(y,x)
- > u
- y x
- [1,] 1 -1
- [2,] 2 -2
- [3,] 3 -3
- > points(u,pch=16,col="blue")
|
|
cbind() の引数の順番を変えれば、値と共にラベルも入れ替わります。異なる図になるのは、ラベルに無関係に順番によって図の x、y が決まっているからだと考えられます。
matrixを使って、値が入れ替わらないように、ラベルを y、x の順に付与してみます。
|
- > x<-0; y<-0
- > plot(x,y,col="white",xlim=c(-5,5),ylim=c(-5,5))
- > x<-c(-1,-2,-3); y<-c(1,2,3)
- > u <- matrix(c(x,y),ncol=2)
- > colnames(u) <- c("x","y")
- > u
- x y
- [1,] -1 1
- [2,] -2 2
- [3,] -3 3
- > points(u,pch=16,col="red")
- > colnames(u) <- c("y","x")
- > u
- y x
- [1,] -1 1
- [2,] -2 2
- [3,] -3 3
- > points(u,col="blue")
|
|
ラベルを入れ替えても、同じ位置に表示され、ラベルは使われないようです。
7. 座標を示す引数の扱い
7.1. 座標を示す引数
原点に点を表示するには、points(0,0,pch=20) などと書きます。
points()などのplot関連の関数の座標は、plot(x,y) のように指定します。x、y は、それぞれ同じ長さのベクトルです。
R は、x<-0 のように単一の値の場合も x[1] と記述して、xだけを書いた場合と同じに扱われます。
plot(0) のように、引数x、yは省略可能です。
引数が1つの場合、yの座標値が与えられたものと解釈されます。xについては、1からの連番が生成されます。x軸はyの値のインデクスになります。したがって、plot(c(0,0)) とすると、(1,0)、(2,0) と2つの点が打たれることになります。
ただし、引数がベクトルではない配列やリストのときには、1つの配列やリストがx、yの両方を含んでいると見なされます。
これは、c(x,y) のようには考えないことを示しています。1つのベクトルに x と y のペアを記録するようには考えず、それぞれ個別のベクトルにあるようになっています。
7.2. x、y の省略
plot() などの引数 x、y の省略は便利です。しかし、自身が作成する関数で同じようにするのが便利なケースはないと思います。省略が有効なケースがあっても、それが 1 からの連番であることはないと思います。
7.3. 1点の座標
lines(c(1,2),c(3,4)) は、(1,3) と (2,4) の間に線を描きます。c(1,2) と c(3,4) の間ではありません。
p1 <- c(1,2)
p2 <- c(3,4)
lines(p1,p2)とは行かず、
lines(c(p1[1],p2[1]), c(p1[2],p2[2])
と記す必要があります。
p1 <- cbind(1,2) のように書けば、x、yの2つのベクトルとなって一貫性がありますが、lines() が lines(p1,p2) と書けるようになる訳ではありません。
自身が作成するプログラムでは、2つの値を持つベクトルを (x,y) と見なすことは避けようがないと思います。
8. 引数や戻り値を揃える
作成する関数の戻り値については、list(x=x,y=y,・・・) のように決めます。そのままplot()などの入力になり、座標以外の値を含めることができます。
引数も、これを受容することになります。
XYZ座標から(緯度、経度)を計算するプログラムで考えてみます。緯度は南緯を負で表し、西経を360°から180°で表します。ただし、弧度です。
すべて、赤道半径を1として計算しています。
- XYZ2φλ <- function(x,y,z, f=0){
- b <- 1-f
- r <- sqrt(x^2+y^2)
- λ <- atan2(y,x)
- λ <- ifelse(abs(λ)<(16*.Machine$double.eps), 0, λ)
- λ <- ifelse(λ<0, 2*pi+λ, λ)
- θ <- atan2(z,r) # 中心角
- φ <- atan(tan(θ)/b^2)
- list(φ=φ, λ=λ)
- }
|
このプログラムは、引数x、y、z が、それぞれ1つの値のときと、それぞれが同じ長さのベクトルのときに機能します。
配列やリストを引数には出来ないので、呼び出し元で、配列やリストの中の対応するベクトルに分けて引数にしています。
配列やリストを引数にするには、以下のようなことが必要です。
- 配列やリストのときは引数の y、z が省略されていなければならない。
- 配列なら、x<-[,1]、y<-[,2]、x<-[,3] とベクトルを取り出す。
- リスト、data.frame なら、x<-$x、y<-$y、x<-$z とベクトルを取り出す。
これらを考慮して書き直してみます。
- XYZ2φλ <- function(x,y,z, f=0){
- b <- 1-f
- if(missing(y) | missing(z)){
- if(missing(y) & missing(z)){
- if(is.list(x)){ y <- x$y; z <- x$z; x <- x$x
- } else if(is.array(x)){ y <- x[,2]; z <- x[,3]; x <- x[,1] }
- else {warning("引数のy,zは省略されているがxはリストでも配列でもない")}
- } else {warning("引数が揃っていない")}
- }
- r <- sqrt(x^2+y^2)
- λ <- atan2(y,x)
- λ <- ifelse(abs(λ)<(16*.Machine$double.eps), 0, λ)
- λ <- ifelse(λ<0, 2*pi+λ, λ)
- θ <- atan2(z,r) # 中心角
- φ <- atan(tan(θ)/b^2)
- list(φ=φ, λ=λ)
- }
|
これで、いろいろな形式の引数に対応できました。
- > x <- c(cos(pi/6),cos(pi/4),cos(pi/3));
- > y <- c(0,0,0);
- > z <- c(sin(pi/6),sin(pi/4),sin(pi/3))
- > str(XYZ2φλ(x[1],y[1],z[1]))
- List of 2
- $ φ: num 0.524
- $ λ: num 0
- > str(XYZ2φλ(x,y,z))
- List of 2
- $ φ: num [1:3] 0.524 0.785 1.047
- $ λ: num [1:3] 0 0 0
- > str(XYZ2φλ(cbind(x,y,z)))
- List of 2
- $ φ: num [1:3] 0.524 0.785 1.047
- $ λ: num [1:3] 0 0 0
- > str(XYZ2φλ(matrix(c(x,y,z),ncol=3)))
- List of 2
- $ φ: num [1:3] 0.524 0.785 1.047
- $ λ: num [1:3] 0 0 0
- > str(XYZ2φλ(list(x=x,y=y,z=z)))
- List of 2
- $ φ: num [1:3] 0.524 0.785 1.047
- $ λ: num [1:3] 0 0 0
- > str(XYZ2φλ(data.frame(x,y,z)))
- List of 2
- $ φ: num [1:3] 0.524 0.785 1.047
- $ λ: num [1:3] 0 0 0
|
要点は、data.frame も is.list() にTRUEが返り、matrix()やcbind()で作った配列もis.array()にTRUEを返すことです。
また、エラーが起きるケースのうち、この関数で決めた引数のルール以外は標準のエラーになることで良いと考えました。
逆向きの変換は以下のように書き直しました。
- φλ2XYZ <- function(φ,λ, f=0){
- b <- 1-f
- if(missing(λ)){
- if(is.list(φ)){
- if(exists("φ",where=φ) & exists("λ",where=φ)){
- λ <- φ$λ; φ <- φ$φ
- } else if(exists("x",where=φ) & exists("y",where=φ)){
- λ <- φ$x; φ <- φ$y
- } else {warning("リストには(φ,λ)か(x,y)が必要")}
- } else if(is.array(x)){ λ <- φ[,2]; φ <- φ[,1] }
- else {warning("引数のλは省略されているがφはリストでも配列でもない")}
- }
- β <- atan2(b*sin(φ),cos(φ))
- list(x=cos(β)*cos(λ),y=cos(β)*sin(λ),z=b*sin(β))
- }
|
9. 常用系との変換
常用系と言っても文字表記ではなく、経度を180°、緯度を90°で表すと言うことです。
経度は東経を正、西経を負で0°から180°で表します。緯度は、北緯を正、南緯を負で 0°から90°で表します。
内部は弧度を使用し、経度は0から2πで東回りで表します。緯度は、赤道が0で北にπ/2、南にーπ/2 まで表します。
- lon2λ <- function(lon){
- (lon %% 360) /180*pi
- }
|
- λ2lon <- function(λ){
- u <- (λ %% (2*pi)) /pi*180
- i <- which(u > 180)
- u[i] <- u[i] - 360
- }
|
|
- lat2φ <- function(lat){
- lat /180*pi
- }
|
- φ2lat <- function(φ){
- φ /pi*180
- }
|
|
10. 航程線を描いて見る
いくらか簡潔になったか航程線を引いてみます。経線は極で1点に集まりますが、メルカトル図法の地図は経線を平行に描きます。したがって、極に近づくほど左右に拡大されます。その率は緯度をφとして 1/cosφ です。これと同じだけ上下にも拡大したものがメルカトル図法の地図のようです。このメルカトル図法の地図で2点間に直線を引いたものを航程線と考え球面に移します。
まず、球面と平面に地図を描いて、2地点に赤い点を描きます。
- clear3d(); rgl.light();
- ee <- ellipsoid(col="white",alpha=0.9); shade3d(ee)
- d <- map("world2Hires",plot=FALSE)
- # 3D
- u <- φλ2XYZ(lat2φ(d$y), lon2λ(d$x))
- lines3d(u)
- YO <- cbind(lat2φ(35.45033), lon2λ(139.63422))
- VA <- cbind(lat2φ(49.266667), lon2λ(-123.116667))
- yo <- φλ2XYZ(YO)
- va <- φλ2XYZ(VA)
- p <- ellipsoid(yo, a=0.02,b=0.02,c=0.02,col="red"); shade3d(p)
- p <- ellipsoid(va, a=0.02,b=0.02,c=0.02,col="red"); shade3d(p)
- # 2D
- MercatorY <- function(φ){ log(tan(pi/4+φ/2)) }
- Mercatorφ <- function(y){ 2*atan(exp(y))-pi/2 }
- x <- d$x/180*pi
- y <- MercatorY(lat2φ(d$y))
- par(mar=c(3,3,1,1),mgp=c(1.5, 0.5, 0))
- plot(x,y,t="l")
- x1 <- YO[2]; y1 <- MercatorY(YO[1])
- x2 <- VA[2]; y2 <- MercatorY(VA[1])
- points(x1,y1,pch=16,col="red")
- points(x2,y2,pch=16,col="red")
|
平面に2点を通る直線を描きます。直線の描画に使った点列の座標値を緯度、経度に直して、球面に描きます。
- # 2点を通る直線の傾きと切片を求めて直線を引く
- a <- (y2-y1)/(x2-x1); b <- (x2*y1-x1*y2)/(x2-x1)
- x <- seq(0,2*pi,length.out=361)
- y <- a*x+b
- lines(x,y,col="blue")
- # 直線を球面に描く
- λ <- x
- φ <- Mercatorφ(y)
- u <- φλ2XYZ(φ, λ)
- lines3d(u,col="blue")
|
map()が返す経度は、最初から360°表示でした。ただし、0°付近は負の値や360°以上になっている箇所があります。
角度は0°から360°未満に丸めたい訳ですが、Rの %% 演算子による剰余はこの目的に都合良く使えます。
剰余はプログラミング言語によって値が異なっています。