西原史暁 (Fumiaki Nishihara)氏のページ,
「Rによるデータクリーニング実践――政府統計からのグラフ作成を例として」
西原氏の作業手順は以下の通り。
1. 整然データを作るという前提
2. すべての年のファイルに以下の処理を行う
注釈的要素の除去
空行や空列の除去
整然データを作るために転置する
転置すると行列になり扱いにくいのでデータフレームに戻す
変数の名前を付ける
「公 立」のような余計な空白の削除
結合が解除されてできる NA を、そのセルの上にある内容で穴埋めする
合計を示すデータの削除
データは文字列として扱われているので整数に変換
元のデータに含まれない調査年を示す列の不可
3. 結果を一つのデータフレームにする
ずいぶんと処理内容が多い。西原氏自身が「この記事は非常に長いものになっている。この長さは、データクリーニングの繁雑さに比例したものである。つまり、データクリーニングが容易ではなく、うんざりするほどのものであることを反映している。自らの手でデータを扱わない人は、この分量を見てデータクリーニングの大変さを感じていただければと思う。」というほどだ。
しかし,手順を逆にすれば,話は比較的簡単になる。
西原氏は,余計なものを除くこと,余計なものでなくすることを目標にしている。しかし,官製 Excel データファイルは,余計なものばかりなのだ。
これに対して,必要なもの(データ)は全体から見ればほんのわずかだ。
やるべき事は各ファイルを読み込み,必要なデータだけを取りだし,一つのデータフレームを作る。
これだけで,データ分析(例えば図を描く)ができるようになる。
整然データにしたければ,そのデータフレームを変換すればよい(多くの場合はそれさえも不要)。
まあ,西原氏の書いたとおり,一定の書式で作られたデータではないが,それでもいくつかの書式に準拠しているのでその規則を利用して,必要なデータを取り出そう。
1986 年のデータ(csv ファイル)は以下のようになっている。
"60 男女別学校数","...2","...3","...4","...5","...6","...7","...8","...9","...10","...11"
NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
NA,NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
"区分","計",NA,NA,"国立","公立",NA,NA,"私立",NA,NA
NA,"計","本校","分校","本校","計","本校","分校","計","本校","分校"
"計","5491","5295","196","17","4178","3990","188","1296","1288","8"
"男女ともにいる学校","4362","4188","174","15","3865","3695","170","482","478","4"
"男のみの学校","400","393","7","1","124","120","4","275","272","3"
"女のみの学校","708","693","15","1","189","175","14","518","517","1"
"生徒のいない学校","21","21","0","0","0","0","0","21","21","0"
"この表は,男子校あるいは女子校という分類ではなく,現実に在学している生徒の状況により分類して集計した。",NA,NA,NA,NA,NA,NA,NA,NA,NA,NA
西原氏がデータ分析の一例として挙げた図を描くには,上の赤字で示した2つの数値(400, 708)があれば十分だ(それ以外の場所の数値も同時に取り出すことは容易だ)。
ファイルを読み取り,"男のみの学校"で始まる行とその次の行の,カンマで区切られた2番目の文字列を整数に変換する。これだけでできるかどうかやってみる。
get.value = function(txt) { # 必要なデータを取り出す関数
a = unlist(strsplit(txt, ",")) # カンマで区切られたデータをバラす
return(as.integer(gsub('"', '', a[2]))) # 2番目のデータを整数にして返す
}
dat = matrix(0, 31, 2)
for (year in 1986:2016) {
file.name = paste0(year, ".csv")
text = readLines(file.name)
for (i in 1:length(text)) {
txt = text[i]
if (grepl("男のみの学校", txt)) { # "男のみの学校"を含む行なら
a = get.value(text[i]) # その行からデータを取り出す
b = get.value(text[i+1]) # 次の行からデータを取り出す
cat(file.name, a, b, "\n")
dat[year-1985, ] = c(a, b)
break # 次のファイルの処理へ
}
}
}
2007 年までのデータはこれで取り出せた。2008 年からは必要なデータの位置が少し違うので,
データを取り出す関数を,以下のようにする。
get.value = function(txt) { # 必要なデータを取り出す関数
a = unlist(strsplit(txt, ",")) # カンマで区切られたデータをバラす
if (a[1] != "NA") { # 最初のデータが NA でなければ,2番目のデータを整数にする
return(as.integer(gsub('"', '', a[2])))
} else if (a[1] == "NA") { # 2008 年以降の場合は最初のデータが NA で,必要なデータは4番目にある
return(as.integer(gsub('"', '', a[4])))
}
}
私もビックリするくらい,実に簡単な規則にそってデータを取り出すことができた。
後はデータフレームにして,列に名前を付ける。必要ならこれを整然データにすれば良いが,そんなことはどうでもよい。
df = data.frame(dat)
colnames(df) = c("male", "female")
rownames(df) = 1986:2016
print(df)
male female
1986 400 708
1987 384 696
1988 366 690
1989 352 689
:
2014 125 320
2015 117 314
2016 112 311
西原氏と同じ図を描いてみよう。ただし,ggplot ではなく,graphics::matplot() で。
old = par(mar=c(4, 4, 1.5, 1), mgp=c(2.5, 0.8, 0), las=1, tck=-0.01)
matplot(df, type="l", ylim=c(0, 700),
xaxt = "n",
xlab = "年", ylab = "学校数",
main="日本における男子のみ・女子のみの高校(通信制除く)の数")
text(25, c(450, 200), c("男子のみ", "女子のみ"))
axis(1, at=1:31, labels=1986:2016)
par(old)
※コメント投稿者のブログIDはブログ作成者のみに通知されます