Работа с данными в R

Меня часто спрашиваютc студенты, вот мы загрузили данные в R, а как их потом обрабатывать? В Excel это всё просто, там есть сводные таблицы, фильтры. А как тут? Неужели все сложно? Нет, отнюдь.

Давайте рассмотрим несколько рецептов на примере данных mtcars. Напоминаю, что команда head выводит первые несколько строк большой таблицы. Чтобы не перегружать заметку я её буду использовать практически в каждой команде.

data("mtcars")
head(mtcars)
##                    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Mazda RX4         21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## Mazda RX4 Wag     21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## Datsun 710        22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## Hornet 4 Drive    21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## Valiant           18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

Сортируем данные

Классический подход к сортировке в R с помощью команды order выглядит так:

head(mtcars[order(mtcars$mpg, mtcars$cyl, mtcars$hp),])
##                      mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Cadillac Fleetwood  10.4   8  472 205 2.93 5.250 17.98  0  0    3    4
## Lincoln Continental 10.4   8  460 215 3.00 5.424 17.82  0  0    3    4
## Camaro Z28          13.3   8  350 245 3.73 3.840 15.41  0  0    3    4
## Duster 360          14.3   8  360 245 3.21 3.570 15.84  0  0    3    4
## Chrysler Imperial   14.7   8  440 230 3.23 5.345 17.42  0  0    3    4
## Maserati Bora       15.0   8  301 335 3.54 3.570 14.60  0  1    5    8

или так, если добавить команду with (чтобы проще записывать названия колонок):

head(with(mtcars,mtcars[order(mpg, cyl, hp),]))
##                      mpg cyl disp  hp drat    wt  qsec vs am gear carb
## Cadillac Fleetwood  10.4   8  472 205 2.93 5.250 17.98  0  0    3    4
## Lincoln Continental 10.4   8  460 215 3.00 5.424 17.82  0  0    3    4
## Camaro Z28          13.3   8  350 245 3.73 3.840 15.41  0  0    3    4
## Duster 360          14.3   8  360 245 3.21 3.570 15.84  0  0    3    4
## Chrysler Imperial   14.7   8  440 230 3.23 5.345 17.42  0  0    3    4
## Maserati Bora       15.0   8  301 335 3.54 3.570 14.60  0  1    5    8

Однако с библиотекой dplyr это выглядит гораздо проще для чтения:

library(dplyr)
head(arrange(mtcars, mpg, cyl, hp))
##    mpg cyl disp  hp drat    wt  qsec vs am gear carb
## 1 10.4   8  472 205 2.93 5.250 17.98  0  0    3    4
## 2 10.4   8  460 215 3.00 5.424 17.82  0  0    3    4
## 3 13.3   8  350 245 3.73 3.840 15.41  0  0    3    4
## 4 14.3   8  360 245 3.21 3.570 15.84  0  0    3    4
## 5 14.7   8  440 230 3.23 5.345 17.42  0  0    3    4
## 6 15.0   8  301 335 3.54 3.570 14.60  0  1    5    8

Единственный недостаток — пропали имена строк. Но, если там была значимая информация, логично было бы держать её в отдельной колонке или воспользоваться пакетом tibble и функцией rownames_to_column.

Далее будут упоминаться еще и другие библиотеки, все они входят в пакет tidyverse. Рекомендую установить его командой install.packages(«tidyverse»).
library(tibble)
head(newcars<-rownames_to_column(mtcars))
##             rowname  mpg cyl disp  hp drat    wt  qsec vs am gear carb
## 1         Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## 2     Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## 3        Datsun 710 22.8   4  108  93 3.85 2.320 18.61  1  1    4    1
## 4    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## 5 Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
## 6           Valiant 18.1   6  225 105 2.76 3.460 20.22  1  0    3    1

Конвейерная обработка

Интересный результат можно получить если использовать конвейерную обработку, сейчас dplyr использует для неё оператор %>% из  пакета magrittr. В приведенном ниже примере, мы говорим, что хотим группировать данные по колонкам cyl и am, отобрать из всех только колонки mpg, cyl, wt, am, причём при группировке по двум колонкам найти средние значения, после чего, результат отфильтровать.

newcars %>% 
  group_by(cyl, am) %>%
  select(mpg, cyl, wt, am) %>%
  summarise(avgmpg = mean(mpg), avgwt = mean(wt)) %>%
  filter(avgmpg > 20)
## # A tibble: 3 x 4
## # Groups:   cyl [2]
##     cyl    am   avgmpg   avgwt
##   <dbl> <dbl>    <dbl>   <dbl>
## 1     4     0 22.90000 2.93500
## 2     4     1 28.07500 2.04225
## 3     6     1 20.56667 2.75500

Полезные рецепты

Привожу несколько распространенных задач на преобразование таблиц.

  1. Отобрать из таблицы только нужные данные, записать все в X, при этом вывести для контроля первые шесть строк.
library(magrittr)
head(X<-newcars %>% filter(cyl == 8))
##              rowname  mpg cyl  disp  hp drat   wt  qsec vs am gear carb
## 1  Hornet Sportabout 18.7   8 360.0 175 3.15 3.44 17.02  0  0    3    2
## 2         Duster 360 14.3   8 360.0 245 3.21 3.57 15.84  0  0    3    4
## 3         Merc 450SE 16.4   8 275.8 180 3.07 4.07 17.40  0  0    3    3
## 4         Merc 450SL 17.3   8 275.8 180 3.07 3.73 17.60  0  0    3    3
## 5        Merc 450SLC 15.2   8 275.8 180 3.07 3.78 18.00  0  0    3    3
## 6 Cadillac Fleetwood 10.4   8 472.0 205 2.93 5.25 17.98  0  0    3    4

Если нужно отобрать текст, а не числа (несколько примеров). Библиотека :

library(stringr)
newcars %>% filter(str_detect(rowname,"RX4"))
##         rowname mpg cyl disp  hp drat    wt  qsec vs am gear carb
## 1     Mazda RX4  21   6  160 110  3.9 2.620 16.46  0  1    4    4
## 2 Mazda RX4 Wag  21   6  160 110  3.9 2.875 17.02  0  1    4    4
newcars %>% filter(str_detect(rowname,"Hornet"))
##             rowname  mpg cyl disp  hp drat    wt  qsec vs am gear carb
## 1    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## 2 Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
newcars %>% filter(str_detect(rowname,"Hornet|RX4"))
##             rowname  mpg cyl disp  hp drat    wt  qsec vs am gear carb
## 1         Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## 2     Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## 3    Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
## 4 Hornet Sportabout 18.7   8  360 175 3.15 3.440 17.02  0  0    3    2
newcars %>% filter(str_detect(rowname,"Hornet|RX4") & cyl==6)
##          rowname  mpg cyl disp  hp drat    wt  qsec vs am gear carb
## 1      Mazda RX4 21.0   6  160 110 3.90 2.620 16.46  0  1    4    4
## 2  Mazda RX4 Wag 21.0   6  160 110 3.90 2.875 17.02  0  1    4    4
## 3 Hornet 4 Drive 21.4   6  258 110 3.08 3.215 19.44  1  0    3    1
  1. Взять из таблицы только нужные колонки данных (mpg, cyl,am), сгруппировать по (cyl, am) и найти среднее по mpg.
head(Y<-newcars %>% 
  group_by(cyl, am) %>%
  select(mpg, cyl,am) %>%
  summarise(avgmpg = mean(mpg)))
## # A tibble: 6 x 3
## # Groups:   cyl [3]
##     cyl    am   avgmpg
##   <dbl> <dbl>    <dbl>
## 1     4     0 22.90000
## 2     4     1 28.07500
## 3     6     0 19.12500
## 4     6     1 20.56667
## 5     8     0 15.05000
## 6     8     1 15.40000
  1. Построить “сводную таблицу”» из полученных данных.
library(tidyr)
## 
## Attaching package: 'tidyr'
## The following object is masked from 'package:magrittr':
## 
##     extract
Y %>% spread(am,avgmpg)
## # A tibble: 3 x 3
## # Groups:   cyl [3]
##     cyl    `0`      `1`
## * <dbl>  <dbl>    <dbl>
## 1     4 22.900 28.07500
## 2     6 19.125 20.56667
## 3     8 15.050 15.40000
  1. Ёще несколько полезных примеров
newcars %>% group_by(cyl, am) %>% summarise_all(c("mean", "sd"))
## Warning in mean.default(rowname): argument is not numeric or logical:
## returning NA
## Warning in var(if (is.vector(x) || is.factor(x)) x else as.double(x), na.rm
## = na.rm): в результате преобразования созданы NA
## # A tibble: 6 x 22
## # Groups:   cyl [?]
##     cyl    am rowname_mean mpg_mean disp_mean   hp_mean drat_mean  wt_mean
##   <dbl> <dbl>        <dbl>    <dbl>     <dbl>     <dbl>     <dbl>    <dbl>
## 1     4     0           NA 22.90000  135.8667  84.66667  3.770000 2.935000
## 2     4     1           NA 28.07500   93.6125  81.87500  4.183750 2.042250
## 3     6     0           NA 19.12500  204.5500 115.25000  3.420000 3.388750
## 4     6     1           NA 20.56667  155.0000 131.66667  3.806667 2.755000
## 5     8     0           NA 15.05000  357.6167 194.16667  3.120833 4.104083
## 6     8     1           NA 15.40000  326.0000 299.50000  3.880000 3.370000
## # ... with 14 more variables: qsec_mean <dbl>, vs_mean <dbl>,
## #   gear_mean <dbl>, carb_mean <dbl>, rowname_sd <dbl>, mpg_sd <dbl>,
## #   disp_sd <dbl>, hp_sd <dbl>, drat_sd <dbl>, wt_sd <dbl>, qsec_sd <dbl>,
## #   vs_sd <dbl>, gear_sd <dbl>, carb_sd <dbl>
newcars %>% summarise_if(is.numeric,c("mean", "sd"))
##   mpg_mean cyl_mean disp_mean  hp_mean drat_mean wt_mean qsec_mean vs_mean
## 1 20.09062   6.1875  230.7219 146.6875  3.596563 3.21725  17.84875  0.4375
##   am_mean gear_mean carb_mean   mpg_sd   cyl_sd  disp_sd    hp_sd
## 1 0.40625    3.6875    2.8125 6.026948 1.785922 123.9387 68.56287
##     drat_sd     wt_sd  qsec_sd     vs_sd     am_sd   gear_sd carb_sd
## 1 0.5346787 0.9784574 1.786943 0.5040161 0.4989909 0.7378041  1.6152
mtcars %>% group_by(cyl) %>% mutate(rank = min_rank(desc(mpg)))
## # A tibble: 32 x 12
## # Groups:   cyl [3]
##      mpg   cyl  disp    hp  drat    wt  qsec    vs    am  gear  carb  rank
##    <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <int>
##  1  21.0     6 160.0   110  3.90 2.620 16.46     0     1     4     4     2
##  2  21.0     6 160.0   110  3.90 2.875 17.02     0     1     4     4     2
##  3  22.8     4 108.0    93  3.85 2.320 18.61     1     1     4     1     8
##  4  21.4     6 258.0   110  3.08 3.215 19.44     1     0     3     1     1
##  5  18.7     8 360.0   175  3.15 3.440 17.02     0     0     3     2     2
##  6  18.1     6 225.0   105  2.76 3.460 20.22     1     0     3     1     6
##  7  14.3     8 360.0   245  3.21 3.570 15.84     0     0     3     4    11
##  8  24.4     4 146.7    62  3.69 3.190 20.00     1     0     4     2     7
##  9  22.8     4 140.8    95  3.92 3.150 22.90     1     0     4     2     8
## 10  19.2     6 167.6   123  3.92 3.440 18.30     1     0     4     4     5
## # ... with 22 more rows
Если кого интересуют дополнительная информация по теме, то её можно прочитать в книге R for Data Science, обложка которой вынесена в качестве иллюстрации к данной статье