<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/asgjpnm7.sav"
url_stu <- foreign::read.spss(url_stu,
jpn09g4stu use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)
15 TIMSSの分析
intsvy
を使えば,TIMSSもPISAと同じように分析することが可能です。 ただ,いくつか違う点もあります。
15.1 ファイル名
TIMSSのデータファイルは,国ごとに分割され,命名規則に従ってファイル名が付けられています。 分析者にとって重要になるのは,主として以下のファイルです。 ここでは第4学年(ファイル名がAで始まる)のファイルを例に説明します。 第8学年のファイルは,ファイル名がBで始まることと, BTM(数学の教員質問)・BTS(理科の教員質問)の二種類が存在する点が違います。
- ACG: 第4学年・学校質問(school context data)
- ASG: 第4学年・生徒質問(student context data)
- ASH: 第4学年・家庭質問(home context data)
- AST: 第4学年・生徒教員リンク(student-teacher linkage)
- ATG: 第4学年・教員質問(teacher context data)
15.2 intsvyによるTIMSSの分析
ここでは最新のTIMSS2019を例に,intsvyによる分析方法を示します。 最初にデータを読み込みます。 TIMSSのデータファイルはSPSSというソフトウェア向けのファイルフォーマットで 公開されているので,Rのforeign
というパッケージにある read.spss
関数を使って読み込みます。 後にintsvy
を使う関係上,read.spss
関数の設定は, 以下のようにしてください。
まず児童に尋ねた家庭のある本の冊数(ASBG04
変数)の回答と, 男女(ITSEX
変数)の人数を確認してみましょう。 いずれの処理も,timss.table
関数で実行できます。 出力は,pisa.table
の場合と同じです。
# 家庭にある本の冊数
::timss.table("ASBG04", data = jpn09g4stu) intsvy
ASBG04 Freq Percentage Std.err.
1 1 595 14.51 0.72
2 2 1220 29.55 0.81
3 3 1545 36.63 0.86
4 4 521 12.12 0.60
5 5 295 6.88 0.42
9 9 14 0.31 0.09
# 性別
::timss.table("ITSEX", data = jpn09g4stu) intsvy
ITSEX Freq Percentage Std.err.
1 1 2038 48.39 0.48
2 2 2158 51.61 0.48
続いて算数・理科の平均値を計算します。 timss.mean.pv
関数を使うと,5つのPVを使い, 平均値の計算とJK法による標準誤差の算出まで行ってくれます。 pisa.mean.pv
関数と同じく,引数by
を指定することで男女(ITSEX
)別や 本の冊数(ASBG04
)別の平均値も計算してくれます。
# 算数の平均点
::timss.mean.pv(paste0("ASMMAT0", 1:5), data = jpn09g4stu) intsvy
Freq Mean s.e. SD s.e
1 4196 592.96 1.75 70.21 1
# 理科の平均点
::timss.mean.pv(paste0("ASSSCI0", 1:5), data = jpn09g4stu) intsvy
Freq Mean s.e. SD s.e
1 4196 561.66 1.77 68.91 1.56
# 男女別
::timss.mean.pv(paste0("ASMMAT0", 1:5), by = "ITSEX", data = jpn09g4stu) intsvy
ITSEX Freq Mean s.e. SD s.e
1 1 2038 593.34 2.18 67.11 1.29
2 2 2158 592.59 1.94 72.99 1.31
# 家にある本の冊数別
::timss.mean.pv(paste0("ASSSCI0", 1:5), by = "ASBG04", data = jpn09g4stu) intsvy
ASBG04 Freq Mean s.e. SD s.e
1 1 595 528.26 3.46 66.41 2.90
2 2 1220 547.75 2.78 66.93 1.96
3 3 1545 572.66 2.20 64.69 2.00
4 4 521 582.04 3.94 64.46 3.02
5 5 295 599.91 4.76 64.51 3.68
6 9 14 487.47 27.14 86.89 18.28
7 <NA> 6 597.36 18.86 81.25 30.20
15.3 学校票の結合
学校票を結合する方法もPISAとほぼ同様です。 以下では,学校票のデータをjpn09g4sch
に格納し,生徒票のデータとmerge
関数で結合しています。
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/acgjpnm7.sav"
url_sch <- foreign::read.spss(url_sch,
jpn09g4sch use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)
<- merge(jpn09g4stu, jpn09g4sch,
jpn09g4v1 by = c(
"IDCNTRY", "IDSCHOOL", "IDPOP", "IDGRADER", "IDGRADE",
"WGTADJ1", "WGTFAC1", "VERSION", "SCOPE"
) )
結合した後は,timss.table
関数などを使って分析が可能です。 以下の例では,経済的に恵まれない家庭の児童の割合(ACBG03A
変数)の回答について, 学校票の回答割合(table
関数を使って度数分布のみ出力)と, timss.table
関数を使った児童の割合を出力しています。
# ACBG03Aの回答(学校単位)
table(jpn09g4sch$ACBG03A)
1 2 3 4
77 56 13 1
round(prop.table(table(jpn09g4sch$ACBG03A)) * 100, 1)
1 2 3 4
52.4 38.1 8.8 0.7
# ACBG03Aの回答(児童単位)
::timss.table("ACBG03A", data = jpn09g4v1) intsvy
ACBG03A Freq Percentage Std.err.
1 1 2207 51.69 4.17
2 2 1569 38.21 4.22
3 3 386 9.34 2.38
4 4 34 0.76 0.76
回答は「1: 0〜10%」「2: 11〜25%」「3: 26〜50%」「4: 50%より多い」の4択です。 度数分布を見ると,1が77校(52.4%)・2が56校(38.1%)・・・であることがわかります。 またtimss.table
の結果から,1の学校に所属する児童の割合が51.69%でもっとも多いことがわかります。
15.4 保護者票の結合
TIMSSでは保護者票を結合することも可能です。 ここでは先ほど学校票を結合したデータフレームjpn09g4v1
にさらに保護者票を結合します。
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/ashjpnm7.sav"
url_par <- foreign::read.spss(url_par,
jpn09g4home use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)<- merge(jpn09g4v1, jpn09g4home,
jpn09g4v2 by = c(
"IDCNTRY", "IDSCHOOL", "IDCLASS", "IDSTUD", "IDPOP", "IDGRADER",
"IDGRADE", "ASDAGE", "ASBGHRL", "ASDGHRL", "VERSION", "SCOPE"
) )
母親の学歴(ASBH15B
変数)の回答割合に注目してみましょう。 ここでは回答者の母親学歴の分布(table
関数で出力), 母親学歴ごとの児童の分布(timss.table
関数で出力), 母親学歴ごとの児童の数学の平均値(timss.mean.pv
関数で出力)のそれぞれを出力します。
# 母親の学歴
table(jpn09g4home$ASBH15B)
3 4 5 6 7 8 9 99
149 1205 108 1672 821 47 7 76
::timss.table("ASBH15B", data = jpn09g4v2) intsvy
ASBH15B Freq Percentage Std.err.
3 3 149 3.63 0.31
4 4 1205 29.97 0.92
5 5 108 2.67 0.24
6 6 1672 40.85 0.79
7 7 821 19.74 0.90
8 8 47 1.12 0.22
9 9 7 0.16 0.06
99 99 76 1.85 0.23
::timss.mean.pv(paste0("ASMMAT0", 1:5), by = "ASBH15B", data = jpn09g4v2) intsvy
ASBH15B Freq Mean s.e. SD s.e
1 3 149 540.60 5.31 62.61 4.45
2 4 1205 575.01 2.16 66.46 1.95
3 5 108 579.11 5.71 57.77 4.26
4 6 1672 599.69 2.31 67.40 1.52
5 7 821 625.30 2.92 65.49 2.09
6 8 47 633.01 9.00 62.05 7.51
7 9 7 548.29 36.66 93.37 32.29
8 99 76 561.35 11.55 74.87 8.18
9 <NA> 111 546.08 9.21 65.53 5.74
母親の学歴の回答は, 「1:学校に行っていない」 「2:小学校」 「3:中学校」 「4:高等学校」 「5:高等学校の専攻科」 「6:短期大学,高等専門学校,専門学校」 「7:大学」 「8:大学院」 「9:あてはまらない」 の9択です。 回答者の人数を見ると, 6(短期大学,高等専門学校,専門学校)が1672人でもっとも多く, ついで4(高等学校)が1205人です。 児童の割合に注目すると,6(短期大学,高等専門学校,専門学校)の母親のいる児童が 全体の40.85%ということになります。 数学の成績に着目すると,基本的に母親の学歴が高いほど(=回答番号が3から8になるにつれて) 児童の成績が高くなることがわかります。 ただし,8(大学院)は保護者の数が少ないこともあって標準誤差(s.e.)が9.00と大きくなっています。
15.5 教員票の結合
TIMSSでは,児童を教えている教員に対しても質問票が配布されています。 教員票を使う場合,ATGで始まる教員票に加え,ASTで始まるリンクファイルもダウンロードする必要があります。
なお,今回のように複数のデータフレームを結合する場合,merge
関数ではどうしても書きにくくなります。 そこで,dplyr
パッケージを使った結合を行っています。 dplyr
はRの処理を円滑に行うために開発されたパッケージの一つです。 現代のRプログラミングでは必須とも言えるものなので, Rに関心のある方は学んでみるとよいでしょう。 dplyr
を使った場合は,データフレーム間で共通する変数が自動的に認識されて結合されます。
以下では分析例として,教員の学歴(ATBG04
変数)の度数分布を表示しています。
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/astjpnm7.sav"
url_l1 <- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/atgjpnm7.sav"
url_l2
<- foreign::read.spss(url_l1,
jpn09g4link use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)<- foreign::read.spss(url_l2,
jpn09g4tea use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)
# dplyrを使うと簡単にマージ可能
suppressMessages(library(dplyr))
<- jpn09g4stu |>
jpn09g4v3 inner_join(jpn09g4link) |>
inner_join(jpn09g4tea)
Joining with `by = join_by(IDCNTRY, IDBOOK, IDSCHOOL, IDCLASS, IDSTUD, IDPOP,
IDGRADER, IDGRADE, JKREP, JKZONE, ASMMAT01, ASMMAT02, ASMMAT03, ASMMAT04,
ASMMAT05, ASSSCI01, ASSSCI02, ASSSCI03, ASSSCI04, ASSSCI05, ASMNUM01, ASMNUM02,
ASMNUM03, ASMNUM04, ASMNUM05, ASMGEO01, ASMGEO02, ASMGEO03, ASMGEO04, ASMGEO05,
ASMDAT01, ASMDAT02, ASMDAT03, ASMDAT04, ASMDAT05, ASMKNO01, ASMKNO02, ASMKNO03,
ASMKNO04, ASMKNO05, ASMAPP01, ASMAPP02, ASMAPP03, ASMAPP04, ASMAPP05, ASMREA01,
ASMREA02, ASMREA03, ASMREA04, ASMREA05, ASSLIF01, ASSLIF02, ASSLIF03, ASSLIF04,
ASSLIF05, ASSPHY01, ASSPHY02, ASSPHY03, ASSPHY04, ASSPHY05, ASSEAR01, ASSEAR02,
ASSEAR03, ASSEAR04, ASSEAR05, ASSKNO01, ASSKNO02, ASSKNO03, ASSKNO04, ASSKNO05,
ASSAPP01, ASSAPP02, ASSAPP03, ASSAPP04, ASSAPP05, ASSREA01, ASSREA02, ASSREA03,
ASSREA04, ASSREA05, ASSENV01, ASSENV02, ASSENV03, ASSENV04, ASSENV05, ASMIBM01,
ASMIBM02, ASMIBM03, ASMIBM04, ASMIBM05, ASSIBM01, ASSIBM02, ASSIBM03, ASSIBM04,
ASSIBM05, VERSION, SCOPE)`
Joining with `by = join_by(IDCNTRY, IDSCHOOL, IDPOP, IDGRADER, IDGRADE,
VERSION, SCOPE, IDTEALIN, IDTEACH, IDLINK, IDSUBJ, ITCOURSE)`
# ATBG04: 教員の学歴
table(jpn09g4tea$ATBG04)
4 5 6
14 255 13
round(prop.table(table(jpn09g4tea$ATBG04)) * 100, 1)
4 5 6
5.0 90.4 4.6
ほとんどの教員が「5: 大卒」であることがわかります。 「4: 短大卒」や「6: 大学院卒」もわずかにいます。
教員票を使った分析をする際に注意することは,標本ウェイトが変わるという点です。 TIMSSでは,児童を担当している教員はすべて教員票の対象になっています。 そのため算数や理科で複数の担任が担当していた場合, 児童1人に対して2名以上の教員票があるということも珍しくありません。 そのため教員票を結合すると,個々の教員に担当している児童が接続され, データセット内に同じ児童のデータが複数存在することになるのです。 このまま平均値などを計算すると,児童がダブルカウント(場合によってはトリプルカウント)されるため, 標本ウェイトを調整し,ダブルカウントの児童のウェイトは半分にするといった処理が行われています。 このウェイトをTIMSSでは教員ウェイト(TCHWGT
)と呼んでいます。 intsvy
では,引数のtimss4_conf
を設定することでウェイトを変更可能です。 ただしこの場合は,timss.mean.pv
ではなく,intsvy.mean.pv
関数を使う必要があります。 以下の例では,ウェイトを変更しなかった場合と変更した場合の推定値を比較しています。 度数(Freq)がjpn09g4v3
では変わっていることも確認してください。
# 正しい推定値(児童票のみ)
::timss.mean.pv(paste0("ASMMAT0", 1:5), data = jpn09g4stu) intsvy
Freq Mean s.e. SD s.e
1 4196 592.96 1.75 70.21 1
# 正しい推定値(学校票・保護者票を追加しても問題ない)
::timss.mean.pv(paste0("ASMMAT0", 1:5), data = jpn09g4v2) intsvy
Freq Mean s.e. SD s.e
1 4196 592.96 1.75 70.21 1
# ウェイトを変更せずに教員票を含むデータセットを使うと間違った推定値が出力される
::timss.mean.pv(paste0("ASMMAT0", 1:5), data = jpn09g4v3) intsvy
Freq Mean s.e. SD s.e
1 7946 593.3 1.88 70.5 1
# timss09g4_tchconfでウェイトを変更
<- intsvy::timss4_conf
timss09g4_tchconf $variables$weight <- "TCHWGT"
timss09g4_tchconf# ウェイトを変更すると適切な推定値が得られる
::intsvy.mean.pv(
intsvypvnames = paste0("ASMMAT0", 1:5),
data = jpn09g4v3, config = timss09g4_tchconf
)
Freq Mean s.e. SD s.e
1 7946 592.96 1.75 70.21 1
# 教員の学歴別に算数の平均値を計算
::intsvy.mean.pv(
intsvypvnames = paste0("ASMMAT0", 1:5), by = "ATBG04",
data = jpn09g4v3, config = timss09g4_tchconf
)
ATBG04 Freq Mean s.e. SD s.e
1 4 353 591.46 5.65 65.12 2.96
2 5 7133 593.30 1.89 70.33 1.02
3 6 358 582.92 5.50 71.14 2.71
4 <NA> 102 626.24 3.01 60.08 7.04
15.6 第8学年のデータを扱う
第8学年のデータも第4学年のデータと同様に分析可能です。 違いはファイル名と,教員票が数学と理科のそれぞれに存在する点です。
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/bsgjpnm7.sav"
url_g8s <- foreign::read.spss(
jpn09g8stu to.data.frame = TRUE, use.value.labels = FALSE, use.missings = FALSE
url_g8s,
)::timss.mean.pv(paste0("BSMMAT0", 1:5), data = jpn09g8stu) intsvy
Freq Mean s.e. SD s.e
1 4446 594.23 2.72 84.14 2.06
以下の例では,第8学年の数学の平均値を教員の学歴(BTBG04
変数)別に出力しています。 最初に,複数のデータファイルをlapply
関数を使って一気にダウンロードしています。 その上でpurrr
のreduce
関数を使って複数のデータフレームを結合し,intsvy.mean.pv
関数で分析しています。 数学の教員ウェイトを使う必要があるので,標本ウェイトはMATWGT
に変更しています。 出力を見ると,標準誤差(s.e.)がNaNになっている箇所がありますが, これは該当する教員が1名しかいないので標準誤差が計算できなかったためです。
# 複数のファイルを一気にダウンロード
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2019/b"
url_g8 <- lapply(c("sg", "st", "tm"), function(x) {
jpn09g8data ::read.spss(paste0(url_g8, x, "jpnm7.sav"),
foreignuse.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)
})# データフレームを結合
<- jpn09g8data |>
jpn09g8 ::reduce(inner_join) purrr
Joining with `by = join_by(IDCNTRY, IDBOOK, IDSCHOOL, IDCLASS, IDSTUD, IDPOP,
IDGRADER, IDGRADE, JKREP, JKZONE, BSMMAT01, BSMMAT02, BSMMAT03, BSMMAT04,
BSMMAT05, BSSSCI01, BSSSCI02, BSSSCI03, BSSSCI04, BSSSCI05, BSMALG01, BSMALG02,
BSMALG03, BSMALG04, BSMALG05, BSMAPP01, BSMAPP02, BSMAPP03, BSMAPP04, BSMAPP05,
BSMDAT01, BSMDAT02, BSMDAT03, BSMDAT04, BSMDAT05, BSMGEO01, BSMGEO02, BSMGEO03,
BSMGEO04, BSMGEO05, BSMKNO01, BSMKNO02, BSMKNO03, BSMKNO04, BSMKNO05, BSMNUM01,
BSMNUM02, BSMNUM03, BSMNUM04, BSMNUM05, BSMREA01, BSMREA02, BSMREA03, BSMREA04,
BSMREA05, BSSAPP01, BSSAPP02, BSSAPP03, BSSAPP04, BSSAPP05, BSSBIO01, BSSBIO02,
BSSBIO03, BSSBIO04, BSSBIO05, BSSCHE01, BSSCHE02, BSSCHE03, BSSCHE04, BSSCHE05,
BSSEAR01, BSSEAR02, BSSEAR03, BSSEAR04, BSSEAR05, BSSKNO01, BSSKNO02, BSSKNO03,
BSSKNO04, BSSKNO05, BSSPHY01, BSSPHY02, BSSPHY03, BSSPHY04, BSSPHY05, BSSREA01,
BSSREA02, BSSREA03, BSSREA04, BSSREA05, BSSENV01, BSSENV02, BSSENV03, BSSENV04,
BSSENV05, BSMIBM01, BSMIBM02, BSMIBM03, BSMIBM04, BSMIBM05, BSSIBM01, BSSIBM02,
BSSIBM03, BSSIBM04, BSSIBM05, VERSION, SCOPE)`
Joining with `by = join_by(IDCNTRY, IDSCHOOL, IDPOP, IDGRADER, IDGRADE,
VERSION, SCOPE, IDTEALIN, IDTEACH, IDLINK, IDSUBJ, ITCOURSE)`
# timss8_confを修正
<- intsvy::timss8_conf
timss09g8m_tchconf $variables$weight <- "MATWGT"
timss09g8m_tchconf# 教員の学歴(BTBG04)ごとの数学の平均値を出力
::intsvy.mean.pv(
intsvypvnames = paste0("BSMMAT0", 1:5), by = "BTBG04",
data = jpn09g8, config = timss09g8m_tchconf
)
BTBG04 Freq Mean s.e. SD s.e
1 4 72 581.96 6.51 73.97 14.05
2 5 5136 595.35 3.15 85.08 2.13
3 6 834 588.82 4.93 77.29 3.12
4 99 36 568.53 NaN 88.02 NaN
5 <NA> 35 587.10 NaN 70.07 NaN
# SEがNAなのは該当する教員が1名しかいないため
table(jpn09g8data[[3]]$BTBG04, exclude = NULL)
4 5 6 99 <NA>
2 179 27 1 1
::intsvy.mean.pv(
intsvypvnames = paste0("BSMMAT0", 1:5),
data = jpn09g8, config = timss09g8m_tchconf
)
Freq Mean s.e. SD s.e
1 6113 594.23 2.72 84.14 2.06
15.7 過去のTIMSS
TIMSS2019の分析方法は,過去のTIMSSにも適用可能です。 たとえばTIMSS2003で,第4学年の日本の平均値を出力する方法は,以下のようになります。
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss2003/ASGJPNm3.sav"
url_03 <- foreign::read.spss(url_03,
jpn03g4stu use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)::timss.mean.pv(paste0("ASSSCI0", 1:5), data = jpn03g4stu) intsvy
Freq Mean s.e. SD s.e
1 4535 543.47 1.54 73.12 1.14
ただし,TIMSS1995だけは現在のTIMSSと調査設計が違うので注意が必要です。 SPSSファイルの場合,JRRの計算に必要なJKREP
変数が小文字になっているので, そのままだとintsvy
が動きません。 PVの変数名も小文字なので,引数もpaste0("asssci0", 1:5)
になります。 さらにTIMSS1995では第3学年も調査対象になっていたので, 第4学年のデータを得るには引数by
に学年(IDGRADE
変数)を指定する必要もあります。
<- "https://raw.githubusercontent.com/kawa5902/LSAdata/refs/heads/main/timss1995/ASGJPNm1.sav"
url_95 <- foreign::read.spss(url_95,
jpn95stu use.value.labels = FALSE,
to.data.frame = TRUE, use.missings = FALSE
)
re-encoding from CP1252
# jkrepをJKREPに変更
names(jpn95stu)[names(jpn95stu) == "jkrep"] <- "JKREP"
# byにIDGRADEを指定し,学年ごとに結果を出力
::timss.mean.pv(paste0("asssci0", 1:5), by = "IDGRADE", data = jpn95stu) intsvy
IDGRADE Freq Mean s.e. SD s.e
1 3 4306 501.03 1.63 73.68 1.29
2 4 4306 553.18 1.74 72.50 1.21