jnobuyukiのブログ

研究していて困ったことやその解決に関するメモ。同じように困ったあなたのために。twitter ID: @j_nobuyuki

R言語で複数の変数を集計

今回は、アンケート項目の集計などに用いるような複数項目の集計方法を紹介します。

rowSums関数の利用

各個人が次のようにアンケートに答えたとします。

参加者ID 質問1 質問2 質問3
A 1 1 0
B 0 1 0
C NA NA NA
D NA 1 0

1は「はい」、0は「いいえ」、NAは未回答とします。
では、それぞれの人が何回「はい」と答えたのかを集計してみたいと思います。こういうときにはrowSums関数が便利です。

dat <- data.frame(ID  = c("A","B","C","D"), Q1 = c(1,0,NA,NA), Q2 = c(1,1,NA,1), Q3 = c(0,0,NA,0))

print(dat)

#totalという変数を追加
dat$total <- rowSums(dat[,2:4])

print(dat)

NA(欠損値)に注意

rowSums関数は NAが一つでも含まれていると合計の結果にNAを戻します。アンケートで言えば、1項目でも未回答だった場合、すべての回答を無効にしてしまいます。これで良い場合もありますが、NAを計算から除外するには、rowSums関数の引数にna.rm = TRUEを加えましょう。

#totalという変数を追加
dat$total <- rowSums(dat[,2:4], na.rm = TRUE)

print(dat)

R言語でデータフレームのデータを並べ替え

今回はR言語でデータを扱うときのちょっとした技の紹介です。

データの並べ替え

複数の変数で構成されるデータフレームオブジェクトについて、任意の変数の昇順で、ケースを並べ替えたい場合は、order関数を利用します。

#データ
data <- data.frame(x = c(1:10), y  = c(2,4,6,8,10,2,4,6,8,10), z = c(100,90,80,70,60,50,40,30,20,10))

print(data)

#yで並べ替え
ysortdata <- data[order(data$y),]

print(ysortdata)


並べ替え変数の1つにタイがあって、並べ替えの変数を追加したければ、orderの引数を増やします。

#yとzで並べ替え
yzsortdata <- data[order(data$y,data$z),]

print(yzsortdata)

昇順ではなく降順にしたい場合は、orderの引数にdecreasgin = TRUEを追加します。

#yを降順で並べ替え
#yで並べ替え
yrsortdata <- data[order(data$y, decreasing = TRUE),]

print(yrsortdata)

子供が使うPCのスペック

今回は、タイトルの内容について思ったことをただそのまま書いてみたいと思います。

予算だけを言えば、できるだけ抑えたい

PCの価格は常に下がり続けています。以前なら30万円を超えるような PCと同等性能のPCが10万円、場合によってはそれ以下で購入できたりします。とはいえ、安い買い物でもないので、できるだけ予算は抑えたい。さらに言えば、子供にせっかく買い与えたPCも落としたり、水をかけたりなど、あっと言う間に壊してしまう可能性を心配してしまいます。となると、最低限使えるスペックでありながら、予算としては10万円を決して越えないレベル。あわよくばいわゆるネットブックで十分と考えたくなります。

中途半端なスペックの PCのデメリット

たまたま、ネットブックではないがそんなにスペックの良くないPCを持っている小学生にScratchなどのブロックプログラミングを紹介する機会を得ました。相手が一人だったこともあって、マンツーマンでScratchを見せようとしたんです。ところが、このPCは全てが3テンポくらい遅い。一瞬「あれ?何か良くないことしてエラー出てるかな?」と思ったころに動きます。動くときは正しく動いているので、バグでも故障でもない。待ち時間は、5秒から10秒くらいだったと思います。

最初は、自分の勤務中に使っているPCと比較してしまうから仕方ないことだと思ってそのままプログラムの使い方を示していました。比較しているのは教えている自分だけで、教わっている小学生はそんなに速いPCがあることを知らないと思ったからです。しかし、何分かそのように教えた後で、自分の考えの誤りに気付きました。プログラムの動作を待つ間、教わっている子がほぼ確実にPC以外の何かに気を逸らされていたからです。それはどこかから聞こえてくる話し声だったり、テーブルに置かれている本だったりします。おそらくその子の気持ちとしては、最初は動くのを待っているが、待っているのにすぐ飽きてしまい、「楽しそうな別の何か」に心移りします。そして完全に心移りした頃に、プログラムが動き、また PCに心が戻ってくる。その繰り返しです。これでは、抽象的な思考を必要とするプログラミングの学習は進まないと思います。

結局大人も子供も予算内で最良のものを買うべき

いやむしろ子供こそ最良のスペックで学習すべきではないかと思います。大人は割り切って待てますが、子供は待てないからです。壊れるのが怖かったら、さらにお金はかかりますが、保険にはいって、買った分を使い尽くせるような方向で準備するのが良いのではないでしょうか?
(プロブロガーではないので、おすすめPCのリンク等は省略させていただきます。)

R言語でパターンマッチング

今回は、R言語正規表現を利用したパターンマッチングの使用例を紹介します。

R言語のパターンマッチング:grepの場合

特定のテキストや文字列のリストに対して、任意のパターンを検索します。文字列がパターンを含んでいるとリスト内の位置を戻り値として出力します。また、パターンを含む文字列を返すこともできます。

characters <- "abcde"
grep("bc",characters)
#1が戻り値になります

charlist <- c("aaaaa","abcde")
grep("bc",charlist)
#2が戻り値になります

chalist <- c("aaaaa","abcde")
grep("bc",charlist, value = TRUE)
#"abcde"が出力されます。

R言語のパターンマッチング:subを利用する場合

sub関数はパターンマッチングした文字列を別の文字列に置換できます。これを利用して、少し特殊な使用方法を紹介します。例えば、何か売り買いをした記録をつけて次のようなデータを作ろうとしたとします。

日にち 行動 金額
1 sell 1000
2 buy 500
3 buy 300
4 sell 800

ところが実際には、行動と金額の間に分割文字を挿入し忘れて、次のようなデータになってしまいました。

日にち 行動金額
1 sell1000
2 buy500
3 buy300
4 sell800

幸いにも行動はアルファベット、金額は数字なので、正規表現でそれぞれを検索できればデータ処理できそうです。ただし、これはgrepではうまくいかないでしょう。grepではマッチするかどうかだけが判定されるからです。そこで、subを使っていらない部分を削除してみたいと思います。

#データ
datalist <- data.frame(date = c(1,2,3,4), behFee = as.character(c("sell1000","buy500","buy300","sell800")))
#sub関数で数字を削除したbehという変数とアルファベットを削除したfeeという変数を作成する。

datalist <- transform(datalist, beh = sub("[[:digit:]]+", replacement = "", behFee), fee = sub("[[:alpha:]]+", replacement = "", behFee))
#feeを数値に変換
datalist$fee <- as.numeric(as.character(datalist$fee))

検索してヒットした文字列をそのまま使うのではなく、それを削除したことで残りを利用するというところがポイントです。

R言語でデータフレームの一部を参照する方法(2)

前回、データフレームの一部の参照についてまとめました。今回はその補足です。

webbeginner.hatenablog.com

リスト形式での参照

データフレーム形式のオブジェクトに以下のようにアクセスするとデータの一部のみが参照されますが、その形式はデータフレームのままになりました。このような参照方法をリスト形式と呼んでいました。

df <- data.frame(x = rnorm(100), y = 1:100)
subdf <- df["x"]

このような形式で呼び出すと、ベクトルオブジェクトを引数に取る関数が使えません。なので以下のコードにはエラーが出ます。

#エラーが出ます
mean(df["x"])

データフレームの内部を参照するための[[

[[を利用すると、データフレームの内容をベクトルオブジェクトとして参照できます。よって、以下のコードでは、エラーを出さずに関数の戻り値がかえってきます。

#エラーが出ずに平均値がかえってきます
mean(df[["x"]])

前回に引き続き、わずかな違いで結果がずいぶん違いますね。使い分けをしっかり覚えたいと思います。

R言語でデータフレームの一部を参照する方法

今回は、データフレームオブジェクトの一部を取り出す方法を紹介します。ほんの少しの違いですが、取り出した結果を他の処理に利用するときには重要な違いになるので、割と大事な内容です。

R言語のオブジェクト

R言語では、いろいろな構造のオブジェクト(ベクトル、マトリックス、データフレームなど)があります。オブジェクトを宣言する際に、構造指定を明示しません。これによって気軽にオブジェクトを宣言できる一方、構造指定の失敗によるエラーに気づきにくというデメリットがあります。複数の変数を含める場合には、マトリックスかデータフレームを用います。この2つのオブジェクトの違いとして、マトリックスはすべての変数のデータ型を揃える必要があるが、データフレームは異なるデータ型の変数を含められるところです。以下のコードを試すと数値型と文字列型のベクトルをマトリックス、データフレームのそれぞれに含めたときの振る舞いを確認できます。元々のデータの型を生かせるという意味では、データフレームの方が扱いやすいと言えます。

#2つ変数に対応するベクトル
a <- c(1,2,3)
b <- c("a","b","c")

#2つのベクトルのデータ型をチェック
class(a)
class(b)

# マトリックス
mat <- matrix(c(a,b),ncol =2)

#マトリックスのデータ構造をチェック
str(mat)

#データフレーム
df <- data.frame(x = a, y = b)

#データフレームのデータ構造をチェック
str(df)

データフレームオブジェクトからデータの取り出し

では、データフレームからデータを抽出する方法をいくつか示します。

取り出す変数の行を指定する

抽出したい変数が、データフレーム内の何列目にあるか知っていれば、列番号で取り出す変数を指定できます。

df <- data.frame(x =c("a","b","c"), y =  c(1,2,3))

#列番号指定によるyの抽出
df[,2]
取り出す変数の列名を指定する

抽出したい変数の名前を列名として指定する方法もあります。

df[,"y"]
取り出す変数名を$で指定する

$を使用する方法でも特定の変数を抽出できます。

df$y
リスト形式で指定する

列名ではなく、該当する変数を直接指定する方法もあります。列名を指定する場合との違いがわずかなので、書き方の違いに注意してください。

df["y"]

抽出方法の違いによるデータ構造の違い

いろいろなデータの取り出し方法があるのですが、方法によって、抽出されたデータの構造が異なります。以下のコードで見てみましょう。

#列番号指定
str(df[,
2])

#列名指定
str(df[,"y"])
str(df$y)

#リスト形式指定
str(df["y"])

上記を試すとリスト形式指定の場合は、抽出されたデータもデータフレーム構造を保持していることがわかります。その他の方法では、取り出されたデータがベクトルとなっています。

まとめ

以上で見たように、データフレームからデータを抽出する方法がいろいろあり、方法によっては、取り出されたデータの構造が異なることがあることを見てきました。例えば取り出したデータの平均値を計算したい場合mean関数を利用できるのは、上3つの方法で、リスト形式では直接mean関数の引数には指定できません。このような違いをきちんと押さえておくことで、バグを減らすことができるのではないかと思います。

R言語でパイプ処理を利用してコードを見やすく

今回は、R言語のコードそのものを見やすくするという話です。

Rでのコードの書き方

R言語では、型を指定しないオブジェクトにデータやその下処理・解析結果を一時的に保持するのが典型的です。例えば以下のコードは、入力データ、下処理後データ、解析結果がそれぞれオブジェクトに保存されています。入力データは、下処理にかけられ、その結果が別のオブジェクトとして保存されます。その結果が、解析にとっての入力になり、さらに結果のオブジェクトが保存されるという流れです。

#入力データ
input <- data.frame(x = rnorm(100), y = rnorm(100))
#下処理
subdata <- subset(input,x > 0)
#解析
result <- lm(y ~ x, data = subdata)

#結果の表示
summary(result)

上記のコードは、一時的な結果を保持するオブジェクトが利用されています。メモリを節約するために、なるべく一時的な保持のオブジェクトを作らないように書くと、次のようになります。

summary(lm(y ~ x, data = subset(data.frame(x = rnorm(100),y = rnorm(100)),x>0)))

確かにメモリは節約できているかもしれませんが、この書き方は関数が複雑な入れ子になっていて、コードが読みにくいものになっています。このように標準的なコードでは、メモリの管理もしくは読みにくさがトレードオフの関係になってしまいがちです。

パイプ処理による解決

上記の問題の解決手段の一つがパイプ処理です。今回は、magrittrというパッケージを利用して、より読みやすいコードの書き方をまとめます*1。なお、magrittrなどのパイプ処理を実現するパッケージの解説記事は、以下がおすすめです。

www.amazon.co.jp

%>%

%>%というパイプ処理では、パイプの左側の結果を、パイプ右側に配置した関数の最初の引数に割り当てます。または、明示的に.で割り当て位置を決定することもできます。試みに上記のコードをパイプ処理を利用して書いてみましょう。

library(magrittr)

input <- data.frame(x = rnorm(100),y = rnorm(100)) %>% subset(x >0) %>% lm(y ~ x, data = .) %>% summary

このように書くとinputとして作成したデータがsubset関数、lm関数、summary関数と順に処理されていく様子を容易に読み取れます。とても便利なのですが、1点注意するとしたら、これがbaseパッケージではできないことです。そのためmagrittrパッケージの導入に同意した人にしかコードをシェアできないということを忘れないようにしましょう。

*1:pipeRというパッケージもあるようです。