50 data.table パッケージを使用したデータ処理

このハンドブックでは、データを整理しグルーピングする方法として、dplyr パッケージの「動詞」関数と magrittr パッケージのパイプ演算子 %>% に焦点を当てています。一方、data.table パッケージは、R を使用していく上で出会うかもしれない、データ処理の代替手段を提供します。

50.1 データテーブルの概要

データテーブル(data table)は、データフレームのような 2 次元のデータ構造であり、複雑なグルーピング操作を実行できます。data.table 構文は、行、列、およびグループに対して操作を実行できるように構成されています。

data.table 構文は DT[i, j, by] と表記され、ijby という 3 つの部品で構成されています。 i 引数を使用すると、必要な行をサブセット化できます。j 引数を使用すると、列を操作できます。by 引数を使用すると、グループごとに列を操作できます。

この章では、以下のトピックについて説明します。

  • データのインポートと fread() および fwrite() の使用
  • i 引数を使用した行の選択とフィルタリング
  • ヘルパー関数 %like%%chin%,、%between% の使用
  • j 引数を使用した列の選択と計算
  • by 引数を使用したグループごとの計算
  • := を使用してデータテーブルにデータを追加および更新する

50.2 パッケージの読み込みとデータのインポート

パッケージの読み込み

pacman パッケージの p_load() 関数を使用して、この分析に必要なパッケージを読み込み(および必要に応じてインストールし)ます。

pacman::p_load(
  rio,        # データのインポート
  data.table, # データのグループ化
  tidyverse,  # この章でパイプ(%>%)関数を使用可にする
  here 
  ) 

データのインポート

この章では、ハンドブック全体で参照されている症例ラインリストを使用して、data.table パッケージのコア機能のいくつかを説明します。

エボラ出血熱の流行をシミュレートしたデータセットをインポートします。お手元の環境でこの章の内容を実行したい方は、クリックして「前処理された」ラインリスト(linelist)データをダウンロードしてください>(.rds 形式で取得できます)。データは rio パッケージの import() を利用してインポートしましょう(rio パッケージは、.xlsx、.csv、.rds など様々な種類のファイルを取り扱うことができます。詳細は、インポートとエクスポート の章をご覧ください。)data.table() を使用してデータフレームをデータテーブルに変換します。

linelist <- rio::import(here("data", "linelist_cleaned.xlsx")) %>% data.table()

fread() 関数は、.csv ファイルなどの普通の区切り付きファイルをデータテーブルに直接インポートするために使用されます。この関数と対を成す fwrite() は、普通の区切り付きファイルへデータテーブルを書き込むために使用されます。これらの関数は、大規模なデータベースにとって非常に高速で計算効率の高い選択肢です。

linelist の最初の 20 行を表示します。

データフレームに使用される dim() などの R の base パッケージ(以下、base R)のコマンドは、データテーブルにも使用できます。

dim(linelist) #データテーブルの行と列の数を示す
## [1] 5888   30

50.3 i 引数: 行の選択とフィルタリング

先ほど説明した DT[i, j, by] data.table 構文を使い、行番号または論理式のいずれかを使用して行をフィルタリングできます。i 引数は構文における最初の引数です。 したがって、DT[i] または DT[i,] 構文を使用できます。

以下の 1 つ目の例ではデータテーブルの最初の 5 行を取得し、2 つ目の例では 18 歳以上の症例をサブセット化し、3 つ目の例では 18 歳以上であるが Central Hospital で診断されていない症例をサブセット化します。

linelist[1:5] #1 行目から 5 行目を取得
linelist[age >= 18] #18 歳以上をサブセット化
linelist[age >= 18 & hospital != "Central Hospital"] #18 歳以上であるが、Central Hospital で診断されていない症例のサブセット

i 引数に .N を使用すると、データテーブルの行の総数を表示します。 この書式は、行番号によるサブセット化に使用できます。

linelist[.N] #最後の行を取得
linelist[15:.N] #15 行目から最後の行を取得

ヘルパー関数を使用したフィルタリング

data.table 構文では、行のサブセット化を容易にするヘルパー関数を使用します。%like% 関数は列内をパターンマッチングで比較するために使用され、%chin% は特定の文字が含まれるか比較するために使用され、%between% 関数は事前に用意した範囲に数字型の列を抽出するために使用されます。

以下では、3 点を例示します。

  • hospital 列に文字列「Hospital」が含まれる行をフィルタリングする
  • 予後が「回復」または「死」である行をフィルタリングする
  • 年齢の範囲が 40〜60 歳の行をフィルタリングする
linelist[hospital %like% "Hospital"] #hospital 列に文字列「Hospital」が含まれる行をフィルタリングする
linelist[outcome %chin% c("Recover", "Death")] #予後が「回復」または「死」である行をフィルタリングする
linelist[age %between% c(40, 60)] #年齢の範囲が 40〜60 歳の行をフィルタリングする

#%between% を使う際は長さ 2 のベクトルが必要だが、%chin% を使う場合は長さ 1 以上のベクトルが使用できる。

50.4 j 引数:列の選択と計算

DT[i, j, by] data.table 構文を使用すると、数値または名前を使用して列を選択できます。j 引数は構文における 2 番目の引数です。 したがって、DT[, j] 構文が使用されます。j 引数の計算を容易にするために、選択する列は list() または .() のいずれかを使用してまとめます。

列の選択

以下の 1 つ目の例では、データテーブルの 1 番目、3 番目、5 番目の列を取得し、2 つ目の例では、身長、体重、性別の列を除くすべての列を選択します。3 つ目の例では、.() を使用して、case_id 列とoutcome 列を選択します。

linelist[ , c(1,3,5)]
linelist[ , -c("gender", "age", "wt_kg", "ht_cm")]
linelist[ , list(case_id, outcome)] #linelist[ , .(case_id, outcome)]でも可

列内の計算

i 引数と j 引数を組み合わせることにより、行をフィルタリングして列内の計算が可能です。 j 引数に .N を使用すると、データテーブルの行の総数を表し、行フィルタリング後に行数を取得するのに役立ちます。

以下では、3 点を例示します。

  • 入院 7 日以上の症例数をカウント
  • “Military” を病院名に含む病院で死亡した症例の平均年齢
  • Central Hospital で回復した症例の年齢の標準偏差、中央値、平均
linelist[days_onset_hosp > 7 , .N]
## [1] 189
linelist[hospital %like% "Military" & outcome %chin% "Death", .(mean(age, na.rm = T))] #na.rm = T は N/A 値を除外します
##         V1
## 1: 15.9084
linelist[hospital == "Central Hospital" & outcome == "Recover", 
                 .(mean_age = mean(age, na.rm = T),
                   median_age = median(age, na.rm = T),
                   sd_age = sd(age, na.rm = T))] #この構文はヘルパー関数を使用しないが、使用する場合と同様に機能する
##    mean_age median_age   sd_age
## 1: 16.85185         14 12.93857

j 引数において .() 構文を使用すると、計算が容易になり、データテーブルを戻り値と返し、列に名前を付けることができます。

50.5 by 引数:グルーピングを用いた計算

by 引数は、DT[i, j, by] data.table 構文の 3 番目の引数です。 by 引数には、文字型ベクトルと list() 構文または .() 構文を使用できます。 by 引数に .() 構文を使用すると、列名をその場で変更できます。

以下では、3 点を例示します。

  • 症例数を病院ごとにグルーピング
  • 18 歳以上の症例を、性別や回復したか死亡したかによってグルーピングし、身長と体重の平均を計算
  • 7 日間以上入院したグループで、入院した月と入院した病院に応じて症例数を集計
linelist[, .N, .(hospital)] #病院別の症例数
##                                hospital    N
## 1:                                Other  885
## 2:                              Missing 1469
## 3: St. Mark's Maternity Hospital (SMMH)  422
## 4:                        Port Hospital 1762
## 5:                    Military Hospital  896
## 6:                     Central Hospital  454
linelist[age > 18, .(mean_wt = mean(wt_kg, na.rm = T),
                             mean_ht = mean(ht_cm, na.rm = T)), .(gender, outcome)] #NA は、データが欠測しているカテゴリを表す
##    gender outcome  mean_wt  mean_ht
## 1:      m Recover 71.90227 178.1977
## 2:      f   Death 63.27273 159.9448
## 3:      m   Death 71.61770 175.4726
## 4:      f    <NA> 64.49375 162.7875
## 5:      m    <NA> 72.65505 176.9686
## 6:      f Recover 62.86498 159.2996
## 7:   <NA> Recover 67.21429 175.2143
## 8:   <NA>   Death 69.16667 170.7917
## 9:   <NA>    <NA> 70.25000 175.5000
linelist[days_onset_hosp > 7, .N, .(month = month(date_hospitalisation), hospital)]
##     month                             hospital  N
##  1:     5                    Military Hospital  3
##  2:     6                        Port Hospital  4
##  3:     7                        Port Hospital  8
##  4:     8 St. Mark's Maternity Hospital (SMMH)  5
##  5:     8                    Military Hospital  9
##  6:     8                                Other 10
##  7:     8                        Port Hospital 10
##  8:     9                        Port Hospital 28
##  9:     9                              Missing 27
## 10:     9                     Central Hospital 10
## 11:     9 St. Mark's Maternity Hospital (SMMH)  6
## 12:    10                              Missing  2
## 13:    10                    Military Hospital  3
## 14:     3                        Port Hospital  1
## 15:     4                    Military Hospital  1
## 16:     5                                Other  2
## 17:     5                     Central Hospital  1
## 18:     5                              Missing  1
## 19:     6                              Missing  7
## 20:     6 St. Mark's Maternity Hospital (SMMH)  2
## 21:     6                    Military Hospital  1
## 22:     7                    Military Hospital  3
## 23:     7                                Other  1
## 24:     7                              Missing  2
## 25:     7 St. Mark's Maternity Hospital (SMMH)  1
## 26:     8                     Central Hospital  2
## 27:     8                              Missing  6
## 28:     9                                Other  9
## 29:     9                    Military Hospital 11
## 30:    10                        Port Hospital  3
## 31:    10                                Other  4
## 32:    10 St. Mark's Maternity Hospital (SMMH)  1
## 33:    10                     Central Hospital  1
## 34:    11                              Missing  2
## 35:    11                        Port Hospital  1
## 36:    12                        Port Hospital  1
##     month                             hospital  N

data.table 構文では、次のように式を組み合わせることもできます。

linelist[, .N, .(hospital)][order(-N)][1:3] #1 番目の構文は病院ごとにすべての症例を選択し、2 番目の構文は症例を降順で並べ替え、3 番目の構文は症例数上位 3 つの病院をサブセット化する
##             hospital    N
## 1:     Port Hospital 1762
## 2:           Missing 1469
## 3: Military Hospital  896

前述の例では、データテーブルの行は新しい症例に相当するという仮定に従っているため、.N を使用してデータテーブルの行数を表すことができます。個別の症例数を表すもう 1 つの便利な関数は、uniqueN() です。この関数は、与えられた入力から、重複のない個別の総数を取得できます。 以下のように実行します。

linelist[, .(uniqueN(gender))] # j 引数の .() はデータテーブルを戻り値として返すことを覚えておいてください
##    V1
## 1:  3

性別の列に含まれる個別の値は m、f、および N/A であるため、戻り値は 3 になります。 与えられた入力の個別の値すべてを取得する base R 関数 unique() と比較してください。

linelist[, .(unique(gender))]
##      V1
## 1:    m
## 2:    f
## 3: <NA>

特定の月における個別の症例数を取得するには、以下のように記述します。

linelist[, .(uniqueN(case_id)), .(month = month(date_hospitalisation))]
##     month   V1
##  1:     5   62
##  2:     6  100
##  3:     7  198
##  4:     8  509
##  5:     9 1170
##  6:    10 1228
##  7:    11  813
##  8:    12  576
##  9:     1  434
## 10:     2  310
## 11:     3  290
## 12:     4  198

50.6 データテーブルへのデータの追加と更新

:= 演算子は、データテーブルへデータを追加または更新するために使用されます。 データテーブルへの列の追加は、以下の方法で実行できます。

linelist[, adult := age >= 18] # 1 列追加
linelist[, c("child", "wt_lbs") := .(age < 18, wt_kg*2.204)] #複数の列を追加するには、c("") と list() または .() 構文が必要
linelist[, `:=` (bmi_in_range = (bmi > 16 & bmi < 40),
                         no_infector_source_data = is.na(infector) | is.na(source))] #この方法では、関数演算子 `:=` として := を使用
linelist[, adult := NULL] #列の削除

より複雑な集計は、入門の範囲を超えています。本章ではデータのグルーピングと整理のために、一般的で実行可能な dplyr パッケージの代替手段を提供することを目的としています。data.table パッケージは、読みやすいコードを可能にする優れたパッケージです。