全国学力テスト模擬データ

文科省が全国学力・学習状況調査(全国学力テスト)のパブリックユースデータ(擬似データ)を公開した。これをRで読み込んで中を調べる方法を解説する。

ここでは生徒データ(Excel:3126KB)つまり中学生(中3)のデータを使う。まずはダウンロードして,Excelで開いてみよう。このような,1行目に変数名,2行目以降に値が並び,セル結合をしていない素直な長方形データは,Rで簡単に扱える。

昔なら,いったんCSVファイルに変換してからRで読み込むところだが,CSVファイルの文字コードによってはRで文字化けが起こったりするので,初心者向きでなかった。今は,RでExcelファイルをそのまま読み込むほうが簡単である。

Excelファイルを読み込む操作は,RStudioならマウスで簡単にできる。右上のペインで「Import Dataset」→「From Excel…」をクリックする(初回使用時にはライブラリのインストールが始まり,しばらく待たされる)。「Browse…」ボタンを押し,ダウンロードしたファイル 1404609_2_1.xlsx を探し,「Open」を押す。データのプレビュー画面が現れる。左下の「Import Options:」で,「Name:」のところ(データの名前)がファイル名から自動生成された「X1404609_2_1」になっている。このままでもよいが,後でわかりやすいように,ここではこれを「seito」に直し,念のためマウスで別の入力欄をクリックする(あるいは Tab キーまたは Enter キーを打つ)。プレビューが再生成されたことを確認してから,右下の「Import」を押す。

もし上で「ここではこれを「seito」に直し」の部分を忘れたなら,後で seito = X1404609_2_1 と打ち込めば,変数名が seito になる。

同じことを素のRで行うには,次のように打ち込む:

library(readxl) # これがエラーになるなら install.packages("readxl") する
seito = read_excel("1404609_2_1.xlsx") # Excelファイルの保存場所に注意

これで seito という変数に生徒データが読み込まれた。

seito  # 中を見てみる
# A tibble: 2,000 x 430
   実施年 地域規模 解答用紙番号  性別 生徒質問紙種別 正答数_国A 正答数_国B
    <dbl>    <dbl>        <dbl> <dbl> <lgl>                <dbl>       <dbl>
 1   2015        3            1     2 NA                      27           7
 2   2015        1            2     2 NA                      26           4
 3   2015        1            3     2 NA                      27           7
 4   2015        2            4     1 NA                      22           7
 5   2015        1            5     2 NA                      27           8
 6   2015        2            6     2 NA                      27           3
 7   2015        4            7     2 NA                      19           7
 8   2015        1            8     1 NA                      23           6
 9   2015        3            9     1 NA                      20           5
10   2015        3           10     1 NA                      32           9
# ... with 1,990 more rows, and 423 more variables: 正答数_数A <dbl>,
#   正答数_数B <dbl>, 正答数_理 <dbl>, 正答数_理A <dbl>, 正答数_理B <dbl>,
#   正答率_国A <dbl>, 正答率_国B <dbl>, 正答率_数A <dbl>, 正答率_数B <dbl>,
#   正答率_理 <dbl>, 正答率_理A <dbl>, 正答率_理B <dbl>, 学力層_国A <dbl>,
#   学力層_国B <dbl>, 学力層_数A <dbl>, 学力層_数B <dbl>, 学力層_理科 <dbl>,
#   学力層_理科A <dbl>, 学力層_理科B <dbl>, 正答数_国A_領域1 <dbl>, …

データ行列の行数・列数を調べる:

dim(seito)
[1] 2000  430

変数名一覧を列挙する:

names(seito)
  [1] "実施年"             "地域規模"           "解答用紙番号"      
  [4] "性別"               "生徒質問紙種別"     "正答数_国A"       
  [7] "正答数_国B"        "正答数_数A"        "正答数_数B"       
 [10] "正答数_理"          "正答数_理A"        "正答数_理B"       
 [13] "正答率_国A"        "正答率_国B"        "正答率_数A"       
 [16] "正答率_数B"        "正答率_理"          "正答率_理A"       
 [19] "正答率_理B"        "学力層_国A"        "学力層_国B"       
(以下略)

1〜19番の変数のサマリー(最小値,第1四分位数,中央値,平均値,第3四分位数,最大値)を出力する:

summary(seito[,1:19])
     実施年        地域規模     解答用紙番号         性別     生徒質問紙種別
 Min.   :2015   Min.   :1.00   Min.   :   1.0   Min.   :1.0   Mode:logical  
 1st Qu.:2015   1st Qu.:2.00   1st Qu.: 500.8   1st Qu.:1.0   NA's:2000     
 Median :2015   Median :3.00   Median :1000.5   Median :1.5                 
 Mean   :2015   Mean   :2.47   Mean   :1000.5   Mean   :1.5                 
 3rd Qu.:2015   3rd Qu.:3.00   3rd Qu.:1500.2   3rd Qu.:2.0                 
 Max.   :2015   Max.   :4.00   Max.   :2000.0   Max.   :2.0                 
  正答数_国A     正答数_国B     正答数_数A     正答数_数B    
 Min.   : 7.00   Min.   :0.000   Min.   : 3.00   Min.   : 0.000  
 1st Qu.:22.00   1st Qu.:5.000   1st Qu.:18.75   1st Qu.: 4.000  
 Median :25.00   Median :6.000   Median :23.00   Median : 6.000  
 Mean   :24.79   Mean   :5.888   Mean   :22.95   Mean   : 6.505  
 3rd Qu.:28.00   3rd Qu.:7.000   3rd Qu.:28.00   3rd Qu.: 9.000  
 Max.   :33.00   Max.   :9.000   Max.   :36.00   Max.   :15.000  
(以下略)

詳しくはデータレイアウト(PDF:784KB)にあるが,実施年はすべて2015,地域規模は1:大都市,2:中核市,3:その他の市,4:町村,性別は1:男子,2:女子,0:不明,生徒質問紙種別はすべて欠測値(NA)である。その次からが正答数と,100点満点に換算した正答率が続く。

国語A,国語B,数学A,数学B,理科の5科目それぞれ45分で答える。Aは基本,Bは応用である。それぞれの満点はデータの最大値に等しい。データでは理科以外に理科A,理科Bがあるが,便宜上二つに分けたようで,基本・応用という区別ではなさそうである。5科目を単純にプロットしてみる:

plot(seito[,6:10])
5科目のプロット

数学Aの正答数分布のヒストグラムを描いてみよう(グラフの例:ヒストグラム参照)。seito の8番目の変数である数学Aの正答数をベクトル(数値の列)として取り出すには seito$正答数_数A または seito[[8]] とする。全角・半角を区別するので seito$正答数_数A ではエラーになる。また,seito[,8] でもいいような気がするが,read_excel() の出力するtibbleというデータ構造は,seito[,8] では1列だけのtibbleとなって,ベクトル(数値の列)を要求する場面ではエラーになる。

hist(seito[[8]], breaks=0:37, right=FALSE)

もうちょっとがんばって綺麗にする:

par(mar=c(2,3,1,1)+0.1)  # 下・左・上・右マージン(デフォルト: c(5,4,4,2)+0.1)
par(mgp=c(2,0.8,0))      # 軸マージン(デフォルト: c(3,1,0))
par(las=1)               # 文字は水平に
hist(seito[[8]], breaks=0:37, right=FALSE,
     main="", freq=FALSE, xlab="", ylab="", xaxt="n")
t = c(0, 10, 20, 30, 36) # 目盛位置
axis(1, t+0.5, t)        # 位置を0.5ずらす

ついでに,平均・標準偏差の等しい正規分布の密度関数を重ね書きしてみる:

curve(dnorm(x, mean(seito[[8]])+0.5, sd(seito[[8]])), add=TRUE, col="#ff990080", lwd=3)
数学Aヒストグラム

国語Aと国語Bの正答率の平均,数学Aと数学Bの正答率の平均,5科目の正答率の平均を求めて,seito に追加する:

seito$正答率_国 = (seito$正答率_国A + seito$正答率_国B) / 2  # あるいは rowMeans(seito[,13:14]
seito$正答率_数 = (seito$正答率_数A + seito$正答率_数B) / 2  # あるいは rowMeans(seito[,15:16]
seito$正答率 = rowMeans(seito[,13:17])

これらの散布図(相関図)を描く:

plot(seito$正答率_国, seito$正答率_数)

もうちょっとがんばってきれいにする。重なりを避けるため,ほんの少し(±0.2)ジッター(ぶれ)を付けてみた:

par(mgp=c(2,0.8,0))      # 軸マージン(デフォルト: c(3,1,0))
plot(jitter(seito$正答率_国,amount=0.2), jitter(seito$正答率_数,amount=0.2),
     xlim=c(0,100), ylim=c(0,100), xlab="国語", ylab="数学")
国語・数学の散布図

半透明の黒丸を使う手もある:

plot(seito$正答率_国, seito$正答率_数, pch=16, col="#00000080",
     xlim=c(0,100), ylim=c(0,100), xlab="国語", ylab="数学")
国語・数学の散布図

例えば男女を別の色にすることによって情報量が増す:

par(mar=c(3,3,1,1)+0.1)  # 下・左・上・右マージン(デフォルト: c(5,4,4,2)+0.1)
par(mgp=c(2,0.8,0))      # 軸マージン(デフォルト: c(3,1,0))
cols = c("#0041ff80","#ff280080")
plot(seito$正答率_国, seito$正答率_数, pch=16, col=cols[seito$性別],
     xlim=c(0,100), ylim=c(0,100), xlab="国語", ylab="数学")
legend("topleft", c("男","女"), pch=16, col=cols)
国語・数学の散布図

相関係数は cor() または cor.test() で求める:

cor.test(seito$正答率_国, seito$正答率_数)

	Pearson's product-moment correlation

data:  seito$正答率_国 and seito$正答率_数
t = 39.736, df = 1998, p-value < 2.2e-16
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.6391802 0.6881876
sample estimates:
      cor 
0.6643974 

数学は男子のほうができるのであろうか?

mean(seito$正答率_数[seito$性別 == 1])
[1] 53.6175
mean(seito$正答率_数[seito$性別 == 2])
[1] 53.49639

次のように書くこともできる:

with(seito, tapply(正答率_数, 性別, mean))
       1        2 
53.61750 53.49639 

男子(1)のほうが女子(2)より多少平均点が高いように見える。箱ひげ図で比べてみよう。

boxplot(正答率_数 ~ 性別, data=seito, ylim=c(0,100), names=c("男","女"))
数学の箱ひげ図

差はなさそうである。念のため t 検定をしてみる:

t.test(seito$正答率_数[seito$性別==1], seito$正答率_数[seito$性別==2])

または同じことであるが次のようにするほうが楽である:

t.test(正答率_数 ~ 性別, data=seito)

結果は次のようになる:

	Welch Two Sample t-test

data:  正答率_数 by 性別
t = 0.14809, df = 1996.7, p-value = 0.8823
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -1.482746  1.724968
sample estimates:
mean in group 1 mean in group 2 
       53.61750        53.49639 

同様に,5科目平均正答率が地域規模と関係しているか,箱ひげ図と分散分析で調べてみる:

boxplot(正答率 ~ 地域規模, data=seito)
oneway.test(正答率 ~ 地域規模, data=seito)

	One-way analysis of means (not assuming equal variances)

data:  正答率 and 地域規模
F = 0.94629, num df = 3.00, denom df = 629.17, p-value = 0.4178

図は略す。有意な差はない。

「生徒質問紙回答_001」は平成27年度全国学力・学習状況調査の調査問題・正答例・解説資料についての「生徒質問紙」によれば「朝食を毎日食べている」で,1:している,2:どちらかといえば,している,3:あまりしていない,4:全くしていないの4択である。それぞれの数は次のようにして求められる:

table(seito$生徒質問紙回答_001)

   1    2    3 
1332  626   42 

boxplot()oneway.test() で調べてみるとよい。