13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【R】 知らず知らず使っている関数『c()』再入門

Last updated at Posted at 2020-03-03

趣旨

普段ベクトルを作成したりするときに使うc()

c("a", "b", "c")
[1] "a" "b" "c"

しかし「ベクトル作る関数だな」程度の認識だった私、apply()mapply()で躓きました。
というわけで 『Rユーザーが最も使う関数c()を改めて見てみましょう。』 という企画。

c()とは?

まずは名前から行きましょう。公式ヘルプを呼び出します。

help(c)

こちらでも同じものが読めます。
R: Combine Values into a Vector or List

タイトルは 『Combine Values into a Vector or List』 です。つまりc()の「c」はCombineに由来するものなんですね。

さらに説明を読むと以下の記述があります。

This is a generic function which combines its arguments.

The default method combines its arguments to form a vector. All arguments are coerced to a common type which is the type of the returned value, and all attributes except names are removed.

まあ要するに、**『引数に渡したオブジェクトをベクトルかリストの形式で結合してくれる関数』**ということですね。
ここで注目しておきたいのは 引き渡すオブジェクトの種類(データ型・構造)に制約を設けていない 点です。つまり、あらゆるオブジェクトを最大公約数的なカタチで結合してくれるようですね。

# install.packages("Hmisc")

char <- "moji"
df <- data.frame(d = 888, 999)

# c() による結合
cmb1 <- c(char, df)

# 結合データの構造を確認
Hmisc::list.tree(cmb1) 
データ構造
 cmb1 = list 2 (528 bytes)
.  [[1]] = character 1= moji 
.  d = double 2= 888 999

このように異なるデータ型(characterを格納したvector + numericを格納したdata.frame)であっても問題なくリスト型データに結合してくれます。

そう、意外と『万能』なんです。

二種類の返り値

c()の返り値は、vector型またはlist型の2種類があります。それによって挙動が微妙に違うようです。

リストによる返り値

こっちのほうが簡単なので先に見ていきましょう。

env <- new.env()
char <- "moji"
num <- 1
int <- as.integer(0)
vec <- c("p", "q")
fct <- factor(c("Z", "Y", "X"), levels = c("X", "Y", "Z"))
mtr <- matrix(3330:3335, 2,3)
lst <- list(l1 = 111, l2 = "LIST")
df <- data.frame(d = c(888, 999))
fun <- function(x, y) x + y

# c() による結合
cmb2 <- c(env, char, num, int, vec, fct, mtr, lst, df, fun)

# 結合データの構造を確認
Hmisc::list.tree(cmb2, maxcomp = length(cmb2))
データ構造
 cmb2 = list 19 (3584 bytes)
.  [[1]] = environment 0
.  [[2]] = character 1= moji 
.  [[3]] = double 1= 1
.  [[4]] = integer 1= 0
.  [[5]] = character 1= p 
.  [[6]] = character 1= q 
.  [[7]] = integer 1= 3
.  [[8]] = integer 1= 2
.  [[9]] = integer 1= 1
.  [[10]] = integer 1= 1
.  [[11]] = integer 1= 2
.  [[12]] = integer 1= 3
.  [[13]] = integer 1= 4
.  [[14]] = integer 1= 5
.  [[15]] = integer 1= 6
.  l1 = double 1= 111
.  l2 = character 1= LIST 
.  d = double 2= 888 999
.  [[19]] = function 1
. A  srcref = integer 8( srcref )= 1 8 1 27 8 27 1 ...
. A A  srcfile = environment 2( srcfilecopy srcfile )

**「単一のベクトルで表現されないデータ構造」**を格納したオブジェクトを1つ以上含めて結合しようとするとlist型データが返却されます。その際、characternumericといった、結合するオブジェクトのデータ型(データ「構造」ではない。)は返却後も保持されています。
上の例では、list型data.frame型environment型funcion型が「単一のベクトルで表現されないデータ構造」に該当します。

ベクトルによる返り値

**「単一のベクトルで表現できるデータ構造」**のみを結合するとvector型データが返却されます。
上の例では、list型data.frame型environment型funcion型が「単一のベクトルで表現されないデータ構造」に該当します。

属性について

注意したいのが、c()による結合は、$names属性以外のデータを保持しない ということです。
よく分からないかもしれませんが、要はオブジェクトのメインコンテンツ以外は切り捨てられるということです。これは冒頭で引用したヘルプにも書いてあることです。

(中略) all attributes except names are removed.

これがどのように影響するのか、具体的には例えば以下のようなことが挙げられます。

結合するデータ型 症状 回避策
data.frame型 data.frame属性が失われるため、list型に変換されてから結合されてしまう。 list()による結合
matrix型 dim属性が失われるため、一行のベクトルに変換されてしまう。 一旦data.frame型へ変換させる。

その上でさらにlist型を回避したい場合は上記を参考。
factor型 levelsordered等の属性情報が失われるため、そのまま結合させるとinteger型のベクトルに変換されてしまう。 ラベルを保持したい場合は、as.vector()等で変換してから結合を行う。

属性全体を保持したい場合は、list()による結合が有効。

list()による結合

以上に関連して、c()を使った結合とlist()による結合には多少の差があります。

c() による結合
> c(df)
$d
[1] 888 999
list() による結合
> list(df)
[[1]]
    d
1 888
2 999
データ構造の比較
# c() による結合
# data.frameがlist型に強制変換されている
> c(df)[[1]]
[1] 888 999
> is.data.frame(c(df)[[1]])
[1] FALSE


# list()&nbsp;による結合
# data.frameが保持されている
> list(df)[[1]]
    d
1 888
2 999

> is.data.frame(list(df)[[1]])
[1] TRUE

このように、c()の場合はデータ構造は一律でvector型またはlist型に変換されますが、list()の場合は元のデータ構造が保持されることになります。
また、matrix型の場合も示していきます。

c() による結合
> c(mtr)
[1] 3330 3331 3332 3333 3334 3335
list() による結合
> list(mtr)
[[1]]
     [,1] [,2] [,3]
[1,] 3330 3332 3334
[2,] 3331 3333 3335

factor型データの結合

もう一つ、factor型を結合したときの挙動を記しておきます。

> c(fct)
[1] 3 2 1

このように順序データとして結合されます。これが望ましいケースもあるので、回避策をするか否かはケースバイケースでご判断ください。

回避策1
# ラベルのほうが残るが、Levels等の属性情報は失われるため、
# 結合後は単なる文字列ベクトルとして扱いたい場合に有効。

> c(as.vector(fct))
[1] "Z" "Y" "X"

回避策2
# すべての属性情報を保持できるため、
# 結合後もfactorとして扱いたい場合には有効。

> list(fct)
[[1]]
[1] Z Y X
Levels: X Y Z

names属性について

ここまでは属性情報は切り捨てられるというお話で進めてきましたが、例外的にnames属性については維持されるようです。

> vec2 <- vec
> names(vec2) <- c("P", "Q")
> c(vec, vec2)
          P   Q 
"p" "q" "p" "q" 

ベクトル結合の注意点

ベクトルを結合する際に、返り値がリストの場合、ベクトルの各要素がリストの各要素に変換されてしまいます。

> c(vec, df)
[[1]]
[1] "p"

[[2]]
[1] "q"

$d
[1] 888 999

これを回避したい場合は、list()による結合か、一度ベクトルをlist型データに格納することをおすすめします。

list() による変換
> list(vec, df)
[[1]]
[1] "p" "q"

[[2]]
    d
1 888
2 999
list() を噛ませた c() による変換
> c(list(vec), df)
[[1]]
[1] "p" "q"

$d
[1] 888 999


# data.frameも保持したい場合は以下のようにする
> c(list(vec), list(df))
[[1]]
[1] "p" "q"

[[2]]
    d
1 888
2 999

オプションについて

c()にはいくつかのオプションが引数で設定できます。

引数 説明 規定値
recursive vector型に強制変換するか FALSE
use.names names属性を保持するか TRUE

recursive

data.frame型との結合のように返り値が通常list型になる場合に、強制的にvector型に変換するかどうかを設定できます。

recursive = F (規定値)
# 
c(df, char)
> c(df, char)
$d
[1] 888 999

[[2]]
[1] "moji"
recursive = T
> c(df, char, recursive = T)
    d1     d2        
 "888"  "999" "moji" 

use.names

名前の通り、names属性を保持するか否かを選択できます。

names = T (規定値)
> c(vec, vec2)
          P   Q 
"p" "q" "p" "q"
names = F
> c(vec, vec2, use.names = F)
[1] "p" "q" "p" "q"

おわりに

基本的な関数ながら意外と万能で奥深い?c()でした。

とはいえ普段はそんなに気にせず使ってもいいと思いますが、たまにトラブルが起きるかもしれませんので、その時はまた見返してもらえたら嬉しいですね。

Enjoy!

おしまい。

参考

13
10
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
13
10

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?