50  Таблица данных

Данное руководство фокусируется на “глагольных” функциях dplyr и операторе канала из пакета magrittr %>% как методах вычистки и группирования данных, но пакетdata.table предлагает альтернативный метод, с которым вы можете столкнуться в ходе своей работы с R.

50.1 Введение в таблицы данных

Таблица данных - это двумерная структура данных, подобная датафрейму, позволяющая выполнять сложные операции группирования. Синтаксис data.table построен таким образом, что операции могут выполняться для строк, столбцов и групп.

Структура - DT[i, j, by], разделенная на 3 части; аргументы i, j и by. Аргумент i позволяет взять подмножество требуемых строк, аргумент j позволяет работать со столбцами, а аргумент by позволяет работать со столбцами по группам.

На этой странице мы рассмотрим следующие темы:

  • Импорт данных и использование fread() и fwrite()
  • Выбор и фильтрация строк, используя аргумент i
  • Использование функций-помощников %like%, %chin%, %between%
  • Выбор и расчет по столбцам, используя аргумент j
  • Расчет по группам, используя аргумент by
  • Добавление и обновление данных в таблицах данных с помощью :=

50.2 Загрузка пакетов и импорт данных

Загрузка пакетов

Используя функцию p_load() из pacman, мы загружаем (и устанавливаем при необходимости) пакеты, требуемые для данного анализа.

pacman::p_load(
  rio,        # импорт данных
  data.table, # группирование и вычистка данных
  tidyverse,  # позволяет использовать функцию оператора канала (%>%) в этой главе
  here 
  ) 

Импорт данных

На данной странице мы рассмотрим некоторые ключевые функции data.table, используя построчный список, на который мы ссылались в других главах этого руководства.

Мы импортируем набор данных о случаях из смоделированной эпидемии лихорадки Эбола. Если вы хотите загрузить данные, чтобы выполнять действия параллельно, см. инструкции в разделе Скачивание руководства и данных. Набор данных импортируется с помощью функции import() из пакета rio. См. страницу Импорт и экспорт, где приведены разные способы импорта данных. С этого момента мы будем использовать data.table(), чтобы конвертировать датафрейм в таблицу данных.

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

Функция fread() используется напрямую для импорта обычных файлов с разделителями, таких как файлы .csv, напрямую в формат таблицы данных. Эта функция и ее парная функция, fwrite(), используемая для записи таблиц данных в виде обычных файлов с разделителями, являются очень быстрыми и вычислительно эффективными вариантами для больших баз данных.

Первыу 20 строк linelist:

Команды базового R, такие как dim(), которые используются для датафреймов, могут также использоваться для таблиц данных

dim(linelist) #дает количество строк и столбцов в таблице данных
[1] 5888   30

50.3 Аргумент i: выбор и фильтрация строк

Вспоминая структуру DT[i, j, by], мы можем фильтровать строки, используя либо номера строк, либо логические выражения. Аргумент i является первым, поэтому можно использовать синтаксис DT[i] или DT[i,].

В первом примере извлекаются первые 5 строк таблицы данных, во втором примере подмножество случаев 18 лет и старше, в третьем примере подмножество случаев 18 лет и старше, но не диагностированных в Central Hospital:

linelist[1:5] #выдает с 1й по 5ю строки
linelist[age >= 18] #подмножество случаев, равных или старше 18 лет
linelist[age >= 18 & hospital != "Central Hospital"] #подмножество случаев, равных или старше 18 лет, но не диагностированных в Central Hospital

Использование .N в аргументе i означает общее количество строк в таблице данных. Это может быть использовано для подмножества по номерам строк:

linelist[.N] #выдает последнюю строку
linelist[15:.N] #выдает с 15й по последнюю строки

Использование функций-помощников для фильтра

В таблице данных используются вспомогательные функции, которые упрощают выделение подмножества строк. Функция %like% испольузется, чтобы сопоставить набор символов в столбце, %chin% используется для сопоставления по конкретному знаку, а функция-помощник %between% используется, чтобы сопоставить числовые столбцы с заранее указанным диапазоном.

В следующем примере мы: * фильтруем строки, где в переменной больницы есть “Hospital” * фильтруем строки, где исход “Recover” (выздоровел) или “Death” (смерть) * фильтруем строки в возрастном диапазоне 40-60

linelist[hospital %like% "Hospital"] #фильтруем строки, где в переменной больницы есть “Hospital”
linelist[outcome %chin% c("Recover", "Death")] #фильтруем строки, где исход “Recover” (выздоровел) или “Death” (смерть)
linelist[age %between% c(40, 60)] #фильтруем строки в возрастном диапазоне 40-60

#%between% должен принять вектор длины 2, а %chin% может принять векторы длины >= 1

50.4 Аргумент j: выбор и расчет по столбцам

Используя структуру DT[i, j, by], мы можем выбирать столбцы с помощью чисел или имен. Аргумент j является вторым, поэтому используется синтаксис DT[, j]. Для облегчения вычислений над аргументом j столбец оборачивается с помощью либо list(), либо .().

Выбор столбцов

Первый пример извлекает первый, третий и пятый столбцы таблицы данных, второй пример выбирает все столбцы, кроме столбцов рост, вес и пол. В третьем примере используется обертка .(), чтобы выбрать столбцы 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, можно фильтровать строки и вычислять по столбцам. Использование .N в аргументе j также представляет общее количество строк в таблице данных и может быть полезно для возврата количества строк после фильтрации строк.

В следующем примере мы: * Считаем количества случаев пребывания в стационаре более 7 дней * Считаем средний возраст больных, умерших в military hospital * Считаем стандартное отклонение, медиану, средний возраст пациентов, выздоровевших в 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
     <num>
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
      <num>      <num>    <num>
1: 16.85185         14 12.93857

Помните, что использование обертки .() в аргументе j упрощает расчет, выдает таблицу данных и позволяет именовать столбцы.

50.5 Аргумент by: расчет по группам

Аргумент by - третий аргумент в структуре DT[i, j, by]. Аргумент by принимает и синтаксис текстового вектора, и list() или .(). Использование синтаксиса .() в аргументе by позволяет на ходу переименовывать столбцы.

В следующем примере мы: * группируем количество случаев по больнице * в случаях 18 лет и старше, рассчитываем средний рост и вес случаев по полу и в зависимости от того, они выздоровели или умерли * в случаях госпитализации, продолжавшейся более 7 дней, подсчитывается количество случаев в зависимости от месяца госпитализации и больницы, в которую они были госпитализированы

linelist[, .N, .(hospital)] #количество случаев по больнице
                               hospital     N
                                 <char> <int>
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
   <char>  <char>    <num>    <num>
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
    <num>                               <char> <int>
 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
              <char> <int>
1:     Port Hospital  1762
2:           Missing  1469
3: Military Hospital   896

В этих примерах мы исходим из того, что строка в таблице данных равна новому случаю, поэтому для представления количества строк в таблице данных можно использовать .N. Другой полезной функцией для представления количества уникальных случаев является uniqueN(), которая выдает количество уникальных значений при заданных входных данных. Это показано ниже:

linelist[, .(uniqueN(gender))] #помните .() в аргументе j выдаст таблицу данных
      V1
   <int>
1:     3

Ответом будет 3, так как уникальные значения в столбце пол - это m, f и N/A. Сравните с функцией базового R unique(), которая выдает все уникальные значения в заданных входных данных:

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

Чтобы найти количество уникальных случаев в заданном месяце, мы напишем следующее:

linelist[, .(uniqueN(case_id)), .(month = month(date_hospitalisation))]
    month    V1
    <num> <int>
 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] #добавляет один столбец
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 является замечательным пакетом, который позволяет использовать удобный и читаемый код.

50.7 Ресурсы

Вот некоторые полезные ресурсы для информации: * https://cran.r-project.org/web/packages/data.table/vignettes/datatable-intro.html * https://github.com/Rdatatable/data.table * https://s3.amazonaws.com/assets.datacamp.com/img/blog/data+table+cheat+sheet.pdf * https://www.machinelearningplus.com/data-manipulation/datatable-in-r-complete-guide/ * https://www.datacamp.com/community/tutorials/data-table-r-tutorial

Вы можете выполнить любую резюмирующую функцию для группированных данных; см. шпаргалку для получения дополнительной информации: https://s3.amazonaws.com/assets.datacamp.com/blog_assets/datatable_Cheat_Sheet_R.pdf