Язык R

и его применение в биоинформатике

Лекция 6

Анна Валяева

6 октября 2023

Применяем функции

Векторизация

Многие функции в R приспособлены к работе с векторами и итерации по элементам вектора. Благодаря этому нам не нужно лишний раз писать циклы.

1:5 * 2
[1]  2  4  6  8 10
1:5 > 3
[1] FALSE FALSE FALSE  TRUE  TRUE
out_vec <- c()

for (i in 1:ncol(airquality)) {
  out_vec[i] = typeof(airquality[, i])
}

out_vec
[1] "integer" "integer" "double"  "integer" "integer" "integer"

Как избегать циклов?

Можно использовать функции из семейства apply:

  • apply()
  • lapply()
  • sapply()
  • tapply()

Как избегать циклов?

Если нужно применить функцию к элементам некоторого вектора или списка, то используйте map из пакета purrr (входит в tidyverse).

library(tidyverse)
# library(purrr)

# вместо
for (i in 1:3){
  f(i)
}

# или
list(f(1), f(2), f(3))

# нужно всего лишь...
map(1:3, f)

Семейство функций map

cube <- function(x) x ** 3

map(1:3, cube)
[[1]]
[1] 1

[[2]]
[1] 8

[[3]]
[1] 27

Разные map_

  • Простой map() всегда возвращает list.
  • Если вы уверены, что ваш результат подходит под определение вектора (данные одного типа), используйте map_:
    • map_chr()
    • map_lgl()
    • map_int()
    • map_dbl()
  • Для других типов данных, которые можно записать в вектор (даты, факторы, …), можно использовать map_vec().

Разные map_

map_chr(mtcars, typeof)
     mpg      cyl     disp       hp     drat       wt     qsec       vs 
"double" "double" "double" "double" "double" "double" "double" "double" 
      am     gear     carb 
"double" "double" "double" 
map_lgl(mtcars, is.double)
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE 
map_int(mtcars, n_distinct)
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
  25    3   27   22   22   29   30    2    2    3    6 
map_vec(c("a", "b", "c"), factor)
[1] a b c
Levels: a b c

Анонимные функции

lambda functions

  • Посчитаем число уникальных значений в каждом столбце mtcars (забыли про n_distinct() из dplyr)
# Полная запись:
map_dbl(mtcars, function(x) length(unique(x)))
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
  25    3   27   22   22   29   30    2    2    3    6 
# Лень писать так много. Так проще:
map_dbl(mtcars, \(x) length(unique(x)))
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
  25    3   27   22   22   29   30    2    2    3    6 
# Устаревший вариант записи:
map_dbl(mtcars, ~ length(unique(.x)))
 mpg  cyl disp   hp drat   wt qsec   vs   am gear carb 
  25    3   27   22   22   29   30    2    2    3    6 

Задание 1

Возьмите встроенный набор данных - msleep.

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

map_df

Удобно использовать для чтения нескольких таблиц одного формата в единый датафрейм.

  • map_dfr - это map() + bind_rows()
  • map_dfc - это map() + bind_cols()
  • bind_rows() и bind_cols() - аналоги rbind() и cbind() из базового R

map_df

  • 3 файла, содержащими таблицы с одинаковыми названиями и порядком столбцов
input_files <- c("file1.csv", "file2.csv", "file3.csv")
  • Муторный вариант:
file1 <- read_csv("file1.csv")
file2 <- read_csv("file2.csv")
file3 <- read_csv("file3.csv")

file <- bind_rows(file1, file2, file3)
  • Оптимальный вариант:
file <- map_dfr(input_files, read_csv)

Вместо map_df

  • С версии purrr 1.0.0 для чтения файлов в один датафрейм рекомендуется использовать map() + list_cbind() или map() + list_rbind()
file <- input_files %>% 
  map(read_csv) %>%     # получился список из датафремов
  list_rbind()          # объединили датафреймы из списка 

map ❤️ списки

  • Плюс новые функции из purrr 1.0.0: list_flatten(), list_simplify(), list_c()
x <- list(
  list(-1, x = 1, y = c(2), z = "a"),
  list(-2, x = 4, y = c(5, 6), z = "b"),
  list(-3, x = 8, y = c(9, 10, 11)))

map_dbl(x, "x") # по имени элемента
[1] 1 4 8
map_dbl(x, list("y", 1)) # по позиции
[1] 2 5 9
map_chr(x, "z", .default = NA) 
[1] "a" "b" NA 

Аргументы функции

x <- list(1:5, c(1:10, NA))
  • Вариант не очень:
map_dbl(x, \(x) mean(x, na.rm = TRUE))
[1] 3.0 5.5
  • Вариант получше:
map_dbl(x, mean, na.rm = TRUE)
[1] 3.0 5.5

map2

  • Если нужно итерировать и по элементам списка, и по вектору аргумента функции.
xs <- map(1:8, \(x) runif(10))
ws <- map(1:8, \(x) rpois(10, 5) + 1)

map2_dbl(xs, ws, weighted.mean, na.rm = TRUE)
[1] 0.4585995 0.4915000 0.5235717 0.6920125 0.6101708 0.4846524 0.4877839
[8] 0.5298169

pmap

  • Когда не хватает map2, а нужен map3 или даже map4
  • Нужно подать список всех аргументов функции.
  • map2(x, y, f) - то же, что и pmap(list(x, y), f).
pmap_dbl(list(xs, ws), weighted.mean)
[1] 0.4585995 0.4915000 0.5235717 0.6920125 0.6101708 0.4846524 0.4877839
[8] 0.5298169

imap

  • imap(x, f) - то же, что и map2(x, seq_along(x), f) или map2(x, names(x), f)
# x - элемент списка
# idx - это название (индекс) элемента списка
x <- map(1:6, \(x) sample(1000, 10))
imap_chr(x,  \(x, idx) paste0("The highest value of ", idx, " is ", max(x)))
[1] "The highest value of 1 is 749" "The highest value of 2 is 998"
[3] "The highest value of 3 is 741" "The highest value of 4 is 976"
[5] "The highest value of 5 is 982" "The highest value of 6 is 975"

Задание 2

Возьмите встроенный набор данных - msleep.

Посчитайте, сколько пропущенных и уникальных значений содержится в каждом из его столбцов. Запишите результаты в датафрейм.

walk

  • Для функций, которые не возращают никакие результаты в R сессию
ggplots <- list(gg1, gg2, gg3)
output_files <- c("plot1.png", "plot2.png", "plot3.png")

walk2(output_files, ggplots, ggsave)

Объединение датафреймов по ключу

  • Как объединить два датафрейма по совпадающим значениям (“ключам”) в одном из столбцов (или нескольких столбцах)?
x <- tribble(
  ~key, ~val_x,
     1, "x1",
     2, "x2",
     3, "x3")
y <- tribble(
  ~key, ~val_y,
     1, "y1",
     2, "y2",
     4, "y3")


inner_join(x, y, by = "key")
# A tibble: 2 × 3
    key val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   

Объединение датафреймов

Объедиение происходит по одному или нескольким столбцам с “ключами”.

  • inner_join() - сохранить только строчки с общими “ключами”
  • full_join() - сохранить все строчки из обеих таблиц
  • left_join() - сохранить все строчки из “первой” таблицы
  • right_join() - сохранить все строчки из “второй” таблицы
full_join(x, y, by = "key")
# A tibble: 4 × 3
    key val_x val_y
  <dbl> <chr> <chr>
1     1 x1    y1   
2     2 x2    y2   
3     3 x3    <NA> 
4     4 <NA>  y3   

Семейство функций reduce

  • reduce() берет на вход вектор длины n и возвращает вектор длины 1, применяя функцию к элементам вектора попарно.
  • Удобно, если надо объединить несколько датафреймов.
# вместо
f(f(f(1, 2), 3), 4)

# нужно всего лишь...
reduce(1:4, f)

reduce2()

Если нужно, например, объединить несколько датафреймов, но использовать разные переменные, по которым делать объединение.

Отслеживать и ловить ошибки 🏠

  • safely() возвращает список из двух элементов:

    • result нужный результат или NULL если была ошибка,
    • error error object или NULL, если ошибки не было.
  • possibly() позволяет использовать default value, если возникает ошибка.

  • quietly() выдает result, output, messages и warnings.

safely() 🏠

  • Можно воспринимать safely() как наречие, а функцию, к которой оно применяется, как глагол
safe_log <- safely(log)
str(safe_log(10))
List of 2
 $ result: num 2.3
 $ error : NULL
str(safe_log("a"))
List of 2
 $ result: NULL
 $ error :List of 2
  ..$ message: chr "non-numeric argument to mathematical function"
  ..$ call   : language .Primitive("log")(x, base)
  ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

safely() 🏠

  • safely() удобно использовать вместе с map
x <- list(1, 10, "a")
y <- x %>% map(safely(log))
str(y)
List of 3
 $ :List of 2
  ..$ result: num 0
  ..$ error : NULL
 $ :List of 2
  ..$ result: num 2.3
  ..$ error : NULL
 $ :List of 2
  ..$ result: NULL
  ..$ error :List of 2
  .. ..$ message: chr "non-numeric argument to mathematical function"
  .. ..$ call   : language .Primitive("log")(x, base)
  .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"

safely() 🏠

Можно отделить результаты от ошибок:

  • Транспонируем список:
y <- transpose(y)
str(y)
List of 2
 $ result:List of 3
  ..$ : num 0
  ..$ : num 2.3
  ..$ : NULL
 $ error :List of 3
  ..$ : NULL
  ..$ : NULL
  ..$ :List of 2
  .. ..$ message: chr "non-numeric argument to mathematical function"
  .. ..$ call   : language .Primitive("log")(x, base)
  .. ..- attr(*, "class")= chr [1:3] "simpleError" "error" "condition"
  • Затем находим элементы, которые вызвали ошибку:
is_ok <- y$error %>% map_lgl(is_null)
x[!is_ok]
[[1]]
[1] "a"

Подробный текст ошибок у map 🏠

  • С версии purrr 1.0.0 текст ошибок map стал более подробным: указывается индекс элемента на котором все сломалось.
  • Но выполнение кода из-за ошибки останавливается.
map(x, log)
Error in `map()`:
ℹ In index: 3.
Caused by error:
! non-numeric argument to mathematical function

possibly() 🏠

Позволяет использовать default value, если возникает ошибка.

x <- list(1, 10, "a")
x %>% map_dbl(possibly(log, otherwise = NA_real_))
[1] 0.000000 2.302585       NA

От purrr к furrr

  • тот же map, но с возможностью параллелизации процессов
# install.packages("furrr")
library(furrr)

# План того, как код должен быть запущен
plan(multisession, workers = 2)

# Код распараллелизован
future_map_dbl(1:3, cube)
[1]  1  8 27

Что почитать про функции и purrr

Работа с табличными данными

Пример - данные про лемуров ай-ай

  • результаты 3 измерений массы тела лемуров в Duke Lemur Center
  • столбец weight_date - номер измерения
  • остальные столбцы - значения массы тела:
    • по столбцу на каждого лемура

lemurs_weights_wide
# A tibble: 3 × 52
  weight_date Agatha Angelique `Annabel Lee` `Ardrey-A` Ardrey `Bellatrix-A`
  <chr>        <dbl>     <dbl>         <dbl>      <dbl>  <dbl> <lgl>        
1 weight_1      1060      2920           944         98   3000 NA           
2 weight_2      1860      2940          1180         98   2780 NA           
3 weight_3      2000       209          1689         95    666 NA           
# ℹ 45 more variables: `Bellatrix-B` <lgl>, `Bellatrix-C` <lgl>,
#   Bellatrix <dbl>, `Blue Devil` <dbl>, Caliban <dbl>, Claudia <dbl>,
#   Cruella <dbl>, Damien <lgl>, Elphaba <dbl>, Endora <dbl>, Goblin <dbl>,
#   Grendel <dbl>, Hitchcock <dbl>, Ichabod <dbl>, Imp <lgl>, Kali <dbl>,
#   Kambana <lgl>, Loki <lgl>, Lucrezia <dbl>, Medea <dbl>, Medusa <dbl>,
#   Mephistopheles <dbl>, Merlin <dbl>, Morticia <dbl>, Niffy <lgl>,
#   `Norman Bates` <dbl>, Nosferatu <dbl>, `Ozma-A` <dbl>, Ozma <dbl>, …

Работа с табличными данными

Очень широкий датафрейм, работать с таким форматом не всегда удобно.

Как привести его к форме name-weight_1-weight_2-weight_3?

lemurs_weights_wide
# A tibble: 3 × 52
  weight_date Agatha Angelique `Annabel Lee` `Ardrey-A` Ardrey `Bellatrix-A`
  <chr>        <dbl>     <dbl>         <dbl>      <dbl>  <dbl> <lgl>        
1 weight_1      1060      2920           944         98   3000 NA           
2 weight_2      1860      2940          1180         98   2780 NA           
3 weight_3      2000       209          1689         95    666 NA           
# ℹ 45 more variables: `Bellatrix-B` <lgl>, `Bellatrix-C` <lgl>,
#   Bellatrix <dbl>, `Blue Devil` <dbl>, Caliban <dbl>, Claudia <dbl>,
#   Cruella <dbl>, Damien <lgl>, Elphaba <dbl>, Endora <dbl>, Goblin <dbl>,
#   Grendel <dbl>, Hitchcock <dbl>, Ichabod <dbl>, Imp <lgl>, Kali <dbl>,
#   Kambana <lgl>, Loki <lgl>, Lucrezia <dbl>, Medea <dbl>, Medusa <dbl>,
#   Mephistopheles <dbl>, Merlin <dbl>, Morticia <dbl>, Niffy <lgl>,
#   `Norman Bates` <dbl>, Nosferatu <dbl>, `Ozma-A` <dbl>, Ozma <dbl>, …

Работа с табличными данными

Как привести его к форме name-weight_1-weight_2-weight_3?

# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha          1060     1860     2000
 2 Angelique       2920     2940      209
 3 Annabel Lee      944     1180     1689
 4 Ardrey-A          98       98       95
 5 Ardrey          3000     2780      666
 6 Bellatrix-A       NA       NA       NA
 7 Bellatrix-B       NA       NA       NA
 8 Bellatrix-C       NA       NA       NA
 9 Bellatrix        585     2760     2460
10 Blue Devil      1330     1820     2460
# ℹ 41 more rows

Работа с табличными данными

Как привести его к форме name-weight_1-weight_2-weight_3?

lemurs_weights <- lemurs_weights_wide %>%                    
  pivot_longer(-weight_date) %>%                             
  pivot_wider(names_from = weight_date, values_from = value) 

lemurs_weights
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha          1060     1860     2000
 2 Angelique       2920     2940      209
 3 Annabel Lee      944     1180     1689
 4 Ardrey-A          98       98       95
 5 Ardrey          3000     2780      666
 6 Bellatrix-A       NA       NA       NA
 7 Bellatrix-B       NA       NA       NA
 8 Bellatrix-C       NA       NA       NA
 9 Bellatrix        585     2760     2460
10 Blue Devil      1330     1820     2460
# ℹ 41 more rows

Как еще можно указать множество столбцов?

  • Использовать информацию о типе данных: where(is.character), where(is.numeric), …
lemurs_weights_wide %>% 
  pivot_longer(!where(is.character)) %>% 
  pivot_wider(names_from = weight_date, values_from = value)

lemurs_weights_wide %>% 
  pivot_longer(where(is.logical) | where(is.numeric)) %>% 
  pivot_wider(names_from = weight_date, values_from = value)

Как еще можно указать множество столбцов?

  • starts_with("pattern") - начинается с pattern
  • ends_with("pattern") - заканчивается на pattern
  • contains("pattern") - содержит подслово pattern
  • matches("pattern") - находится по регулярному выражению pattern
lemurs_weights %>% select(starts_with("weight")) %>% head(1)
# A tibble: 1 × 3
  weight_1 weight_2 weight_3
     <dbl>    <dbl>    <dbl>
1     1060     1860     2000
lemurs_weights %>% select(matches("*_[12]")) %>% head(1)
# A tibble: 1 × 2
  weight_1 weight_2
     <dbl>    <dbl>
1     1060     1860

Как еще можно указать множество столбцов?

  • num_range() - поиск по общему префиксу среди столбцов с некой нумерацией
lemurs_weights %>% 
  select(num_range("weight_", c(1,3))) %>% # prefix, numeric range
  head(1) 
# A tibble: 1 × 2
  weight_1 weight_3
     <dbl>    <dbl>
1     1060     2000

Как еще можно указать множество столбцов?

  • Использовать информацию о позиции столбца
lemurs_weights %>% select(1, num_range("weight_", c(1,3))) %>% head(1)
# A tibble: 1 × 3
  name   weight_1 weight_3
  <chr>     <dbl>    <dbl>
1 Agatha     1060     2000

Как еще можно указать множество столбцов?

  • Добавить условие по значениям в столбцах
lemurs_weights %>% 
  select(where(~ is.numeric(.) && max(., na.rm=TRUE) > 3000)) %>% 
  head(1)
# A tibble: 1 × 1
  weight_3
     <dbl>
1     2000

Устаревшее:

lemurs_weights %>% 
  select_if(~ is.numeric(.) && max(., na.rm=TRUE) > 3000)

Как еще можно указать множество столбцов?

  • Использовать вектор с названиями нужных столбцов и all_of() или any_of().
weight_cols <- paste("weight", 1:4, sep = "_")


lemurs_weights %>% 
  select(all_of(weight_cols)) 
Error in `all_of()`:
! Can't subset columns that don't exist.
✖ Column `weight_4` doesn't exist.
lemurs_weights %>% 
  select(any_of(weight_cols)) %>% 
  head(1)
# A tibble: 1 × 3
  weight_1 weight_2 weight_3
     <dbl>    <dbl>    <dbl>
1     1060     1860     2000

Трансформация таблиц

Задача: по 3 взвешиваниям посчитать средний вес каждого лемура.

lemurs_weights
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha          1060     1860     2000
 2 Angelique       2920     2940      209
 3 Annabel Lee      944     1180     1689
 4 Ardrey-A          98       98       95
 5 Ardrey          3000     2780      666
 6 Bellatrix-A       NA       NA       NA
 7 Bellatrix-B       NA       NA       NA
 8 Bellatrix-C       NA       NA       NA
 9 Bellatrix        585     2760     2460
10 Blue Devil      1330     1820     2460
# ℹ 41 more rows

Подсчет по нескольким столбцам

Задача: по 3 взвешиваниям посчитать средний вес каждого лемура.

Получилось что-то странное…

lemurs_weights %>% 
  mutate(
    avg_weight = mean(weight_1:weight_3))
# A tibble: 51 × 5
   name        weight_1 weight_2 weight_3 avg_weight
   <chr>          <dbl>    <dbl>    <dbl>      <dbl>
 1 Agatha          1060     1860     2000       1530
 2 Angelique       2920     2940      209       1530
 3 Annabel Lee      944     1180     1689       1530
 4 Ardrey-A          98       98       95       1530
 5 Ardrey          3000     2780      666       1530
 6 Bellatrix-A       NA       NA       NA       1530
 7 Bellatrix-B       NA       NA       NA       1530
 8 Bellatrix-C       NA       NA       NA       1530
 9 Bellatrix        585     2760     2460       1530
10 Blue Devil      1330     1820     2460       1530
# ℹ 41 more rows

Подсчет по нескольким столбцам

Задача: по 3 взвешиваниям посчитать средний вес каждого лемура.

Получилось верно, но как-то глупо считать среднее “вручную”…

lemurs_weights %>% 
  mutate(
    avg_weight = (weight_1 + weight_2 + weight_3) / 3)
# A tibble: 51 × 5
   name        weight_1 weight_2 weight_3 avg_weight
   <chr>          <dbl>    <dbl>    <dbl>      <dbl>
 1 Agatha          1060     1860     2000      1640 
 2 Angelique       2920     2940      209      2023 
 3 Annabel Lee      944     1180     1689      1271 
 4 Ardrey-A          98       98       95        97 
 5 Ardrey          3000     2780      666      2149.
 6 Bellatrix-A       NA       NA       NA        NA 
 7 Bellatrix-B       NA       NA       NA        NA 
 8 Bellatrix-C       NA       NA       NA        NA 
 9 Bellatrix        585     2760     2460      1935 
10 Blue Devil      1330     1820     2460      1870 
# ℹ 41 more rows

Подсчет по нескольким столбцам

Группируем построчно и для каждой строки считаем среднее. Каждый лемур сам себе группа.

lemurs_weights %>% 
  rowwise() %>% 
  mutate(
    avg_weight = mean(c(weight_1, weight_2, weight_3), na.rm = TRUE))
# A tibble: 51 × 5
# Rowwise: 
   name        weight_1 weight_2 weight_3 avg_weight
   <chr>          <dbl>    <dbl>    <dbl>      <dbl>
 1 Agatha          1060     1860     2000      1640 
 2 Angelique       2920     2940      209      2023 
 3 Annabel Lee      944     1180     1689      1271 
 4 Ardrey-A          98       98       95        97 
 5 Ardrey          3000     2780      666      2149.
 6 Bellatrix-A       NA       NA       NA       NaN 
 7 Bellatrix-B       NA       NA       NA       NaN 
 8 Bellatrix-C       NA       NA       NA       NaN 
 9 Bellatrix        585     2760     2460      1935 
10 Blue Devil      1330     1820     2460      1870 
# ℹ 41 more rows

Подсчет по нескольким столбцам

c_across() позволяет отбирать столбцы по-умному (как срез, по типу данных, …).

lemurs_weights %>% 
  rowwise() %>% 
  mutate(
    avg_weight = mean(c_across(weight_1:weight_3), na.rm = TRUE)) 
# A tibble: 51 × 5
# Rowwise: 
   name        weight_1 weight_2 weight_3 avg_weight
   <chr>          <dbl>    <dbl>    <dbl>      <dbl>
 1 Agatha          1060     1860     2000      1640 
 2 Angelique       2920     2940      209      2023 
 3 Annabel Lee      944     1180     1689      1271 
 4 Ardrey-A          98       98       95        97 
 5 Ardrey          3000     2780      666      2149.
 6 Bellatrix-A       NA       NA       NA       NaN 
 7 Bellatrix-B       NA       NA       NA       NaN 
 8 Bellatrix-C       NA       NA       NA       NaN 
 9 Bellatrix        585     2760     2460      1935 
10 Blue Devil      1330     1820     2460      1870 
# ℹ 41 more rows

Подсчет по нескольким столбцам

c_across() позволяет отбирать столбцы по-умному (как срез, по типу данных, …).

lemurs_weights %>% 
  rowwise() %>% 
  mutate(
    avg_weight = mean(c_across(where(is.numeric)), na.rm = TRUE)) 
# A tibble: 51 × 5
# Rowwise: 
   name        weight_1 weight_2 weight_3 avg_weight
   <chr>          <dbl>    <dbl>    <dbl>      <dbl>
 1 Agatha          1060     1860     2000      1640 
 2 Angelique       2920     2940      209      2023 
 3 Annabel Lee      944     1180     1689      1271 
 4 Ardrey-A          98       98       95        97 
 5 Ardrey          3000     2780      666      2149.
 6 Bellatrix-A       NA       NA       NA       NaN 
 7 Bellatrix-B       NA       NA       NA       NaN 
 8 Bellatrix-C       NA       NA       NA       NaN 
 9 Bellatrix        585     2760     2460      1935 
10 Blue Devil      1330     1820     2460      1870 
# ℹ 41 more rows

Подсчет по нескольким столбцам

rowwise() создает группировку, которую умеет снимать summarise() или ungroup().

lemurs_weights %>% 
  rowwise() %>% 
  mutate(
    avg_weight = mean(c_across(where(is.numeric)), na.rm = TRUE)) %>% 
  ungroup() 
# A tibble: 51 × 5
   name        weight_1 weight_2 weight_3 avg_weight
   <chr>          <dbl>    <dbl>    <dbl>      <dbl>
 1 Agatha          1060     1860     2000      1640 
 2 Angelique       2920     2940      209      2023 
 3 Annabel Lee      944     1180     1689      1271 
 4 Ardrey-A          98       98       95        97 
 5 Ardrey          3000     2780      666      2149.
 6 Bellatrix-A       NA       NA       NA       NaN 
 7 Bellatrix-B       NA       NA       NA       NaN 
 8 Bellatrix-C       NA       NA       NA       NaN 
 9 Bellatrix        585     2760     2460      1935 
10 Blue Devil      1330     1820     2460      1870 
# ℹ 41 more rows

Трансформировать сразу все столбцы

Столбцы перезаписываются.

lemurs_weights %>%  
  mutate(across(everything(), toupper))
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>       <chr>    <chr>    <chr>   
 1 AGATHA      1060     1860     2000    
 2 ANGELIQUE   2920     2940     209     
 3 ANNABEL LEE 944      1180     1689    
 4 ARDREY-A    98       98       95      
 5 ARDREY      3000     2780     666     
 6 BELLATRIX-A <NA>     <NA>     <NA>    
 7 BELLATRIX-B <NA>     <NA>     <NA>    
 8 BELLATRIX-C <NA>     <NA>     <NA>    
 9 BELLATRIX   585      2760     2460    
10 BLUE DEVIL  1330     1820     2460    
# ℹ 41 more rows

Трансформировать несколько столбцов

lemurs_weights %>%  
  mutate(across(c("name"), toupper)) 
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 AGATHA          1060     1860     2000
 2 ANGELIQUE       2920     2940      209
 3 ANNABEL LEE      944     1180     1689
 4 ARDREY-A          98       98       95
 5 ARDREY          3000     2780      666
 6 BELLATRIX-A       NA       NA       NA
 7 BELLATRIX-B       NA       NA       NA
 8 BELLATRIX-C       NA       NA       NA
 9 BELLATRIX        585     2760     2460
10 BLUE DEVIL      1330     1820     2460
# ℹ 41 more rows

Трансформировать несколько столбцов

lemurs_weights %>%  
  mutate(across(where(is.numeric), round))
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha          1060     1860     2000
 2 Angelique       2920     2940      209
 3 Annabel Lee      944     1180     1689
 4 Ardrey-A          98       98       95
 5 Ardrey          3000     2780      666
 6 Bellatrix-A       NA       NA       NA
 7 Bellatrix-B       NA       NA       NA
 8 Bellatrix-C       NA       NA       NA
 9 Bellatrix        585     2760     2460
10 Blue Devil      1330     1820     2460
# ℹ 41 more rows

Трансформировать несколько столбцов

lemurs_weights %>%  
  mutate(across(starts_with("weight"), \(x) x / 1000))  
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha         1.06     1.86     2    
 2 Angelique      2.92     2.94     0.209
 3 Annabel Lee    0.944    1.18     1.69 
 4 Ardrey-A       0.098    0.098    0.095
 5 Ardrey         3        2.78     0.666
 6 Bellatrix-A   NA       NA       NA    
 7 Bellatrix-B   NA       NA       NA    
 8 Bellatrix-C   NA       NA       NA    
 9 Bellatrix      0.585    2.76     2.46 
10 Blue Devil     1.33     1.82     2.46 
# ℹ 41 more rows

Трансформировать несколько столбцов

lemurs_weights %>%  
  mutate(across(starts_with("weight"), ~ .x / 1000))  
# A tibble: 51 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha         1.06     1.86     2    
 2 Angelique      2.92     2.94     0.209
 3 Annabel Lee    0.944    1.18     1.69 
 4 Ardrey-A       0.098    0.098    0.095
 5 Ardrey         3        2.78     0.666
 6 Bellatrix-A   NA       NA       NA    
 7 Bellatrix-B   NA       NA       NA    
 8 Bellatrix-C   NA       NA       NA    
 9 Bellatrix      0.585    2.76     2.46 
10 Blue Devil     1.33     1.82     2.46 
# ℹ 41 more rows

Трансформировать несколько столбцов

При использовании list(...) или при указании .names = ... создаются новые столбцы.

lemurs_weights %>%  
  mutate(across(starts_with("weight"), list(kg = ~ .x / 1000))) 
# A tibble: 51 × 7
   name        weight_1 weight_2 weight_3 weight_1_kg weight_2_kg weight_3_kg
   <chr>          <dbl>    <dbl>    <dbl>       <dbl>       <dbl>       <dbl>
 1 Agatha          1060     1860     2000       1.06        1.86        2    
 2 Angelique       2920     2940      209       2.92        2.94        0.209
 3 Annabel Lee      944     1180     1689       0.944       1.18        1.69 
 4 Ardrey-A          98       98       95       0.098       0.098       0.095
 5 Ardrey          3000     2780      666       3           2.78        0.666
 6 Bellatrix-A       NA       NA       NA      NA          NA          NA    
 7 Bellatrix-B       NA       NA       NA      NA          NA          NA    
 8 Bellatrix-C       NA       NA       NA      NA          NA          NA    
 9 Bellatrix        585     2760     2460       0.585       2.76        2.46 
10 Blue Devil      1330     1820     2460       1.33        1.82        2.46 
# ℹ 41 more rows

Переименовать несколько столбцов

Сначала указываем, как модифицировать названия столбцов, затем - какие столбцы.

lemurs_weights %>%  
  mutate(across(starts_with("weight"), list(kg = ~ .x/1000))) %>% 
  rename_with(~ str_c("KG_", str_remove(., "_kg")), ends_with("kg")) 
# A tibble: 51 × 7
   name        weight_1 weight_2 weight_3 KG_weight_1 KG_weight_2 KG_weight_3
   <chr>          <dbl>    <dbl>    <dbl>       <dbl>       <dbl>       <dbl>
 1 Agatha          1060     1860     2000       1.06        1.86        2    
 2 Angelique       2920     2940      209       2.92        2.94        0.209
 3 Annabel Lee      944     1180     1689       0.944       1.18        1.69 
 4 Ardrey-A          98       98       95       0.098       0.098       0.095
 5 Ardrey          3000     2780      666       3           2.78        0.666
 6 Bellatrix-A       NA       NA       NA      NA          NA          NA    
 7 Bellatrix-B       NA       NA       NA      NA          NA          NA    
 8 Bellatrix-C       NA       NA       NA      NA          NA          NA    
 9 Bellatrix        585     2760     2460       0.585       2.76        2.46 
10 Blue Devil      1330     1820     2460       1.33        1.82        2.46 
# ℹ 41 more rows

Трансформировать несколько столбцов

transmute оставляет только перечисленные и новые столбцы.

lemurs_weights %>%  
  transmute(
    name, 
    across(starts_with("weight"), list(kg = ~ .x/1000), .names = "KG_{.col}"))  
# A tibble: 51 × 4
   name        KG_weight_1 KG_weight_2 KG_weight_3
   <chr>             <dbl>       <dbl>       <dbl>
 1 Agatha            1.06        1.86        2    
 2 Angelique         2.92        2.94        0.209
 3 Annabel Lee       0.944       1.18        1.69 
 4 Ardrey-A          0.098       0.098       0.095
 5 Ardrey            3           2.78        0.666
 6 Bellatrix-A      NA          NA          NA    
 7 Bellatrix-B      NA          NA          NA    
 8 Bellatrix-C      NA          NA          NA    
 9 Bellatrix         0.585       2.76        2.46 
10 Blue Devil        1.33        1.82        2.46 
# ℹ 41 more rows

summarise по нескольким столбцам с группировкой

lemurs
# A tibble: 51 × 6
   name           sex   weight_1 weight_2 weight_3 birth_type
   <chr>          <chr>    <dbl>    <dbl>    <dbl> <chr>     
 1 Nosferatu      M         2860     2505     2930 wild      
 2 Poe            M         2700     2610     2680 wild      
 3 Samantha       F         2242     2360     2415 wild      
 4 Annabel Lee    F          944     1180     1689 captive   
 5 Mephistopheles M         2760     2520     2620 wild      
 6 Endora         F         2600     2360     2645 wild      
 7 Ozma           F         2500     2440     2620 wild      
 8 Morticia       F         2700     2550     2255 wild      
 9 Blue Devil     M         1330     1820     2460 captive   
10 Goblin         M         1180     1460     1150 captive   
# ℹ 41 more rows

summarise по нескольким столбцам с группировкой

lemurs %>%  
  group_by(sex, birth_type) %>% 
  summarise(across(starts_with("weight"), mean, na.rm = TRUE))
# A tibble: 5 × 5
# Groups:   sex [3]
  sex   birth_type weight_1 weight_2 weight_3
  <chr> <chr>         <dbl>    <dbl>    <dbl>
1 F     captive       1604.    1490.    1722.
2 F     wild          2510.    2428.    2484.
3 M     captive       1893.    1589.    1410.
4 M     wild          2773.    2545     2743.
5 <NA>  captive        NaN      NaN      NaN 

summarise по нескольким столбцам с группировкой

lemurs %>%  
  drop_na(sex) %>% 
  group_by(sex, birth_type) %>% 
  summarise(across(starts_with("weight"), \(x) mean(x, na.rm = TRUE))) 
# A tibble: 4 × 5
# Groups:   sex [2]
  sex   birth_type weight_1 weight_2 weight_3
  <chr> <chr>         <dbl>    <dbl>    <dbl>
1 F     captive       1604.    1490.    1722.
2 F     wild          2510.    2428.    2484.
3 M     captive       1893.    1589.    1410.
4 M     wild          2773.    2545     2743.

summarise по нескольким столбцам с группировкой…

Посчитаем средний вес в разных измерениях по группам, затем посчитаем для этих групп средний вес по измерениям.

lemurs %>%  
  drop_na(sex) %>% 
  group_by(sex, birth_type) %>% 
  summarise(across(starts_with("weight"), mean, na.rm = TRUE)) %>% 
  ungroup() %>% 
  rowwise() %>% 
  mutate(avg_weight = mean(c_across(starts_with("weight")), na.rm = TRUE))
# A tibble: 4 × 6
# Rowwise: 
  sex   birth_type weight_1 weight_2 weight_3 avg_weight
  <chr> <chr>         <dbl>    <dbl>    <dbl>      <dbl>
1 F     captive       1604.    1490.    1722.      1605.
2 F     wild          2510.    2428.    2484.      2474.
3 M     captive       1893.    1589.    1410.      1631.
4 M     wild          2773.    2545     2743.      2687.

Подсчет наблюдений 🏠

  • tally() - количество набюдений (строк) всего
  • add_tally() - добавляется отдельный столбец с общим количеством наблюдений
lemurs %>% tally()
# A tibble: 1 × 1
      n
  <int>
1    51
lemurs %>%
  select(1:2) %>% 
  add_tally()
# A tibble: 51 × 3
   name           sex       n
   <chr>          <chr> <int>
 1 Nosferatu      M        51
 2 Poe            M        51
 3 Samantha       F        51
 4 Annabel Lee    F        51
 5 Mephistopheles M        51
 6 Endora         F        51
 7 Ozma           F        51
 8 Morticia       F        51
 9 Blue Devil     M        51
10 Goblin         M        51
# ℹ 41 more rows

Подсчет наблюдений в группах 🏠

  • count() - количество набюдений (строк) в группе
  • add_count() - добавляется отдельный столбец с количеством наблюдений в группе
lemurs %>% count(sex)
# A tibble: 3 × 2
  sex       n
  <chr> <int>
1 F        26
2 M        24
3 <NA>      1
lemurs %>%
  select(1:2) %>% 
  add_count(sex)
# A tibble: 51 × 3
   name           sex       n
   <chr>          <chr> <int>
 1 Nosferatu      M        24
 2 Poe            M        24
 3 Samantha       F        26
 4 Annabel Lee    F        26
 5 Mephistopheles M        24
 6 Endora         F        26
 7 Ozma           F        26
 8 Morticia       F        26
 9 Blue Devil     M        24
10 Goblin         M        24
# ℹ 41 more rows

Фильтрация данных 🏠

  • between()
lemurs %>% 
  filter(between(weight_1, 900, 1100))
  • near()
lemurs %>% 
  filter(near(weight_1, 1000, tol = 100)) # от 900 до 1100
# A tibble: 2 × 6
  name        sex   weight_1 weight_2 weight_3 birth_type
  <chr>       <chr>    <dbl>    <dbl>    <dbl> <chr>     
1 Annabel Lee F          944     1180     1689 captive   
2 Agatha      F         1060     1860     2000 captive   

Фильтрация данных 🏠

  • Отбираем те наблюдения weight_1, которые лежат в пределах 1 SD от среднего
lemurs %>% 
  filter(near(weight_1, 
              mean(weight_1, na.rm = TRUE),      
              tol = sd(weight_1, na.rm = TRUE))) 
# A tibble: 24 × 6
   name           sex   weight_1 weight_2 weight_3 birth_type
   <chr>          <chr>    <dbl>    <dbl>    <dbl> <chr>     
 1 Poe            M         2700     2610     2680 wild      
 2 Samantha       F         2242     2360     2415 wild      
 3 Annabel Lee    F          944     1180     1689 captive   
 4 Mephistopheles M         2760     2520     2620 wild      
 5 Endora         F         2600     2360     2645 wild      
 6 Ozma           F         2500     2440     2620 wild      
 7 Morticia       F         2700     2550     2255 wild      
 8 Blue Devil     M         1330     1820     2460 captive   
 9 Goblin         M         1180     1460     1150 captive   
10 Cruella        F         2050     1350     2340 captive   
# ℹ 14 more rows

Фильтрация по нескольким столбцам

lemurs %>%  
  filter(across(starts_with("weight"), ~ . > 2500)) 
# A tibble: 3 × 6
  name           sex   weight_1 weight_2 weight_3 birth_type
  <chr>          <chr>    <dbl>    <dbl>    <dbl> <chr>     
1 Nosferatu      M         2860     2505     2930 wild      
2 Poe            M         2700     2610     2680 wild      
3 Mephistopheles M         2760     2520     2620 wild      

Фильтрация по нескольким столбцам

  • if_any оставляет те строки, где хотя бы в одном из указанных столбцов условие выполняется.
lemurs_weights %>%  
  filter(if_any(starts_with("weight"), ~ . > 3000)) 
# A tibble: 1 × 4
  name     weight_1 weight_2 weight_3
  <chr>       <dbl>    <dbl>    <dbl>
1 Lucrezia     2560      213     3070

Фильтрация по нескольким столбцам

  • if_all оставляет те строки, где во всех указанных столбцах условие выполняется.
lemurs_weights %>%  
  filter(if_all(starts_with("weight"), ~ . > 2500)) 
# A tibble: 3 × 4
  name           weight_1 weight_2 weight_3
  <chr>             <dbl>    <dbl>    <dbl>
1 Mephistopheles     2760     2520     2620
2 Nosferatu          2860     2505     2930
3 Poe                2700     2610     2680

Фильтрация пропущенных значений 🏠

Все значения должны быть не NA.

lemurs_weights %>% 
  drop_na(where(is.numeric)) %>% head(3)
# A tibble: 3 × 4
  name        weight_1 weight_2 weight_3
  <chr>          <dbl>    <dbl>    <dbl>
1 Agatha          1060     1860     2000
2 Angelique       2920     2940      209
3 Annabel Lee      944     1180     1689
lemurs_weights %>% 
  filter(if_all(where(is.numeric), ~ !is.na(.x))) %>% head(3)
# A tibble: 3 × 4
  name        weight_1 weight_2 weight_3
  <chr>          <dbl>    <dbl>    <dbl>
1 Agatha          1060     1860     2000
2 Angelique       2920     2940      209
3 Annabel Lee      944     1180     1689

Фильтрация пропущенных значений 🏠

Хоть одно значение не NA.

lemurs_weights %>% 
  filter(if_any(where(is.numeric), ~ !is.na(.x))) 
# A tibble: 37 × 4
   name        weight_1 weight_2 weight_3
   <chr>          <dbl>    <dbl>    <dbl>
 1 Agatha          1060    1860      2000
 2 Angelique       2920    2940       209
 3 Annabel Lee      944    1180      1689
 4 Ardrey-A          98      98        95
 5 Ardrey          3000    2780       666
 6 Bellatrix        585    2760      2460
 7 Blue Devil      1330    1820      2460
 8 Caliban         2080     296.      641
 9 Claudia          740    2550      2390
10 Cruella         2050    1350      2340
# ℹ 27 more rows

Что почитать про продвинутый dplyr

Конец!