Язык программирования R для анализа данных: лекция 3

Елена Убогоева

Повторим материал предыдущей лекции

  • Матрицы, списки, датафреймы

  • Условия и циклы

  • Основы векторизации

План лекции

  • Операции над векторами разной длины, ресайклинг.

  • Создание собственных функций.

  • Семейство функций *apply().

  • Импорт и экспорт данных в R.

  • Описательные статистики

Операции над векторами

Что произойдет в результате выполнения этого кода?

vector1 <- c(1, 1, 2, 3, 5, 8)
vector2 <- c(2, 3, 5, 8, 13, 21)
vector1 + vector2
[1]  3  4  7 11 18 29

Произойдет поэлементное сложение каждого элемента первого вектора с элементами второго вектора.

Можно проводить любые операции над векторами одной длины: сложение, вычитание, умножение, возведение в степень и тд. Это очень полезно и является одним из преимуществ R для анализа данных.

Что произойдет, если провести подобные операции над векторами разной длины?

Операции над векторами разной длины

Теперь представим, что векторы у нас разной длины, попробуем сделать сложение и вычитание:

long_vector <- c(1, 1, 2, 3, 5, 8)
short_vector <- c(2, 3, 5)

long_vector + short_vector
[1]  3  4  7  5  8 13
long_vector - short_vector
[1] -1 -2 -3  1  2  3

Значения вектора меньшего размера как бы переиспользуются (recycling) для значений вектора большего.

Ресайклинг одного значения

Можно умножить вектор на какое-то значение (такое задание уже было в домашней работе). Здесь тоже сработает правило ресайклинга: единственное значение вектора переиспользуется каждый раз.

long_vector * 3
[1]  3  3  6  9 15 24
long_vector ^ 2
[1]  1  1  4  9 25 64

Тоже самое для логических операций:

long_vector == 3
[1] FALSE FALSE FALSE  TRUE FALSE FALSE
long_vector > 2
[1] FALSE FALSE FALSE  TRUE  TRUE  TRUE

И даже (хотя такое не рекомендуется):

long_vector == short_vector
[1] FALSE FALSE FALSE FALSE FALSE FALSE

Что произойдет при ресайклинге некратных векторов?

Для проверки длины векторов можно использовать функцию length()

vector2 <- c(3, 5, 1, 4)
length(long_vector)
[1] 6
length(vector2)
[1] 4
long_vector - vector2
Warning in long_vector - vector2: длина большего объекта не является
произведением длины меньшего объекта
[1] -2 -4  1 -1  2  3

Ресайклинг некратных векторов не рекомендуется к использованию из-за непредсказуемости аутпута.

Зачем нужен ресайклинг векторов?

  • Чтобы удивлять друзей

  • Наиболее полезен ресайклинг одного значения, например, чтобы сделать преобразование.
    x / mean(x), x / 1000000

  • Обращать внимание, если ресайклинг произошел не так, как было задумано.

Pitfalls - подводные камни при ресайклинге

  • Нужно знать о правилах ресайклинга, чтобы понять, почему что-то может получиться не так, как было задумано. Допустим, у нас есть вектор с различными значениями, и мы хотим извлечь определенные, попробуем использовать оператор ==:
eyes <- c('green', 'blue', 'black', 'blue', 'green', 'green')
eyes[eyes == c('green', 'blue')]
[1] "green" "blue"  "blue"  "green"

Вроде все правильно. Или все же нет?

Здесь сработало правило ресайклинга, которое привело к неправильному результату без уведомлений об ошибке, что особенно неприятно

  • eyes == c('green', 'blue')
    [1]  TRUE  TRUE FALSE  TRUE  TRUE FALSE

Оператор %in% для проверки наличия элементов вектора

Правильно проверять, есть ли элементы в векторе, нужно с помощью оператора %in%, в который можно подать целый вектор значений.

eyes %in% c('green', 'blue')
[1]  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
eyes[eyes %in% c('green', 'blue')]
[1] "green" "blue"  "blue"  "green" "green"

Полезные функции для работы с векторами

Иногда бывает полезно извлечь индексы элементов TRUE логического вектора, тогда можно использовать функцию which().

eyes %in% c('green', 'blue')
[1]  TRUE  TRUE FALSE  TRUE  TRUE  TRUE
which(eyes %in% c('green', 'blue'))
[1] 1 2 4 5 6

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

Однако вот такая запись избыточна:

eyes[which(eyes %in% c('green', 'blue'))]
[1] "green" "blue"  "blue"  "green" "green"
# достаточно использовать логическую индексацию
eyes[eyes %in% c('green', 'blue')]
[1] "green" "blue"  "blue"  "green" "green"

Полезные функции для работы с векторами

Для проверки, есть ли хоть один TRUE в векторе используется функция any(), для проверки, все ли значения в векторе TRUE, есть функция all().

any(c(FALSE, TRUE, FALSE, FALSE))
[1] TRUE
any(c(FALSE, FALSE, FALSE))
[1] FALSE
all(c(FALSE, TRUE, FALSE, FALSE))
[1] FALSE
all(c(TRUE, TRUE, TRUE))
[1] TRUE

Создание собственных функций

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

Преимущества написания функций:

  • Способ избежать ошибок copy-paste, например, есть риск не поменять название переменной в одном месте.

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

  • Можно давать функции понятное название, что облегчит восприятие и читаемость кода.

Пример собственной функции

Функции пишутся по шаблону:

name <- function(arguments) {
  body
}

Например, создадим функцию, которая возводит заданное число в заданную степень.

pow <- function(value, p) {
  power <- value ^ p
  return(power)
}
pow(11, 4)
[1] 14641

Написание своих функций

  • Когда функция встречает ключевое слово return, выполнение функции прекращается и возвращается подсчитанное значение (все, что после return будет проигнорировано).

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

# вот так работать не будет
pow <- function(x, p) {
  power <- x ^ p
}
pow(11, 4)
# а так будет
pow <- function(x, p) {
  x ^ p
}
pow(11, 4)
[1] 14641
pow <- function(x, p) {
  power <- x ^ p
  power
}
pow(11, 4)
[1] 14641

Рекомендации по написанию функций

  • return все же рекомендуется к использованию, чтобы повысить читаемость кода.

  • Необходимо давать фукциям понятные названия, обычно советуют использовать глаголы.

  • Для функций, которые планируется использовать не только для себя, рекомендуется прописать проверку входных значений и обработку исключений.

Почитать можно детальнее здесь (на английском) и здесь (на русском).

Позиционные и именованные аргументы

Как многие могли заметить для встроенных функций в R, иногда мы задаем в явном виде названия аргументов, а иногда полагаемся на порядок.

rep(c(10, 15), 2) # вариант по умолчанию
[1] 10 15 10 15
rep(c(10, 15), times = 2)
[1] 10 15 10 15
rep(c(10, 15), each = 2) # вариант с использованием each
[1] 10 10 15 15
rep(x = c(10, 15), times = 2, each = 2)
[1] 10 10 15 15 10 10 15 15
rep(each = 2, times = 2, x = c(10, 15)) # аргументы в другом порядке, но заданы по именам
[1] 10 10 15 15 10 10 15 15

Создание своей полезной функции

Попробуем создать полезную функцию, вычисляющую моду - наиболее часто встречающееся значение в выборке. Здесь будут полезны функции table() и which.max() - возвращает индекс максимального значения

set.seed(42) # чтобы случайное семплирование воспроизводилось
x <- sample(1:5, 15, replace = TRUE)
x
 [1] 1 5 1 1 2 4 2 2 1 4 1 5 4 2 2
calculate_mode <- function(x) {
  if (is.character(x)) {
    return(names(table(x)[which.max(table(x))]))
  }
  return(as.numeric(names(table(x)[which.max(table(x))])))
}
calculate_mode(x)
[1] 1
calculate_mode(c('a', 'b', 'a', 'a', 'c', 'd'))
[1] "a"

Простые числа

is_prime <- function(x) {
  if (x == 1) return(FALSE)
  if (x == 2) return(2)
  if (sum(x %% 2:(x-1) == 0) == 0) return(x) else return(FALSE)
}

Локальное и глобальное окружение

При создании функции у нее есть свое собственное окружение (локальное). В этом локальном окружении существуют переменные функции.

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

y <- 10
func1 <- function(x) {
  y + x ^ 2
}
func1(10)
[1] 110

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

Созданные переменные внутри функции не попадают в глобальное окружение.

Аргументы по умолчанию

Нередко встречается ситуация, когда какой-то аргумент функции используется чаще других. В таком случае есть смысл задать аргумент по умолчанию, например в функции sort(), по умолчанию аргумент decreasing = FALSE, следовательно, функция сортирует по возрастанию.

sort(c(1, 4, 2, -1, 10, 5))
[1] -1  1  2  4  5 10
sort(c(1, 4, 2, -1, 10, 5), decreasing = TRUE)
[1] 10  5  4  2  1 -1

Можно самостоятельно задавать их для своих функций.

Ленивые вычисления (lazy evaluation)

Аргументы функции могут не использоваться, если их не вызвать эксплицитно, поскольку в R есть концепция ленивых вычислений.

lazy_function <- function(value, arg_never_used) {
  return(value ^ 2) 
}
lazy_function(10)
[1] 100

Ленивые вычисления позволяют обращаться к переменной, только если ее использование необходимо.

Функциональное программирование - функция apply()

Сама функция apply() применяется к матрицам или многомерным массивам. Можно применить apply() к датафрейму, но он сначала превратится в матрицу.

Например, задача посчитать среднее по всем столбцам матрицы.

Синтаксис: apply(array, margin, function), где margin - измерение, по которому будет итерироваться функция function, 1 - это строки, 2 - столбцы.

M <- matrix(1:20, nrow = 4)
M
     [,1] [,2] [,3] [,4] [,5]
[1,]    1    5    9   13   17
[2,]    2    6   10   14   18
[3,]    3    7   11   15   19
[4,]    4    8   12   16   20

Функциональное программирование - функция apply()

apply(M, 1, mean) # подсчет среднего по строкам
[1]  9 10 11 12
apply(M, 2, mean) # подсчет среднего по столбцам
[1]  2.5  6.5 10.5 14.5 18.5

Попробуем применить apply() к датафрейму mtcars

apply(mtcars, 2, mean) # по столбцам
       mpg        cyl       disp         hp       drat         wt       qsec 
 20.090625   6.187500 230.721875 146.687500   3.596563   3.217250  17.848750 
        vs         am       gear       carb 
  0.437500   0.406250   3.687500   2.812500 

Обратите внимание!

Здесь функция mean без круглых скобок, поскольку используется не сама функция, а название функции является аргументом функции apply().

Семейство функций *apply()

Существует целый зоопарк сходных функций: lapply, vapply, sapply, tapply, mapply, однако на практике чаще используется lapply, а также функции семейства map*() из библиотеки purrr.

Наиболее полезная функция lapply() (от слов list apply), которая принимает на вход вектор или лист и выполняет заданную функцию.

lapply(list(rnorm(10), 
            rnorm(10, sd = 2), rnorm(10, sd = 5)), sd)
[[1]]
[1] 1.630584

[[2]]
[1] 2.312116

[[3]]
[1] 5.576344

Используем свои функции в apply()

Давайте используем нашу простую функцию, которая вычисляет моду в данных.

apply(iris, 2, calculate_mode)
Sepal.Length  Sepal.Width Petal.Length  Petal.Width      Species 
       "5.0"        "3.0"        "1.4"        "0.2"     "setosa" 
lapply(mtcars, calculate_mode)
$mpg
[1] 10.4

$cyl
[1] 8

$disp
[1] 275.8

$hp
[1] 110

$drat
[1] 3.07

$wt
[1] 3.44

$qsec
[1] 17.02

$vs
[1] 0

$am
[1] 0

$gear
[1] 3

$carb
[1] 2

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

Анонимная функция - функция, которая создается и используется “на лету” в функциях apply().

Распространенная практика использовать анонимные функции, если действие помещается в одну строчку.
Синтаксис в lapply() такой: function(i) <действие>, где i - это i-тый элемент листа, для которого выполняется действие.

lapply(11:20, function(i) rep(i, 10))
[[1]]
 [1] 11 11 11 11 11 11 11 11 11 11

[[2]]
 [1] 12 12 12 12 12 12 12 12 12 12

[[3]]
 [1] 13 13 13 13 13 13 13 13 13 13

[[4]]
 [1] 14 14 14 14 14 14 14 14 14 14

[[5]]
 [1] 15 15 15 15 15 15 15 15 15 15

[[6]]
 [1] 16 16 16 16 16 16 16 16 16 16

[[7]]
 [1] 17 17 17 17 17 17 17 17 17 17

[[8]]
 [1] 18 18 18 18 18 18 18 18 18 18

[[9]]
 [1] 19 19 19 19 19 19 19 19 19 19

[[10]]
 [1] 20 20 20 20 20 20 20 20 20 20

Переходим от *apply() -> purrr::map_*()

Часто можно встретить функцию sapply() (от слов simplified apply), которая упрощает выдачу. Однако в нынешней мете R не рекомендуется к использованию из-за непредсказуемости аутпута.

sample_list <- list(rnorm(10), rnorm(10, sd = 2), rnorm(10, sd = 5))
sapply(sample_list, sd)
[1] 0.8622293 2.4225496 3.4741834
# сравните с lapply
lapply(sample_list, sd)
[[1]]
[1] 0.8622293

[[2]]
[1] 2.42255

[[3]]
[1] 3.474183

Переходим от *apply() -> purrr::map_*()

Чтобы контролировать формат аутпута используем функции семейства map_*() из библиотеки purrr.

В простом случае синтаксис map() не отличим от lapply(), аутпут всегда будет списком.

library(purrr)
map(sample_list, sd)
[[1]]
[1] 0.8622293

[[2]]
[1] 2.42255

[[3]]
[1] 3.474183

Можно задать желаемый аутпут с помощью постфикса, например map_chr() - аутпут будет строковым вектором, map_dbl() - числовой вектор.

map_dbl(sample_list, sd)
[1] 0.8622293 2.4225496 3.4741834

Туториал по библиотеке purrr

Супер крутая презентация от Charlotte Wickham

Ссылка на туториал

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

Загрузка таблиц:

df <- read.table('путь/к/файлу/название_файла.txt')

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

Допустим, у нас лежит файл wc3_units.txt в директории data, попробуем его прочитать.

df <- read.table('data/wc3_units.txt', sep = '\t', header = TRUE)
head(df)
      Unit  Race Gold Wood Pop  HP Armor.Type Armor Sight Speed Time
1  Peasant Human   75    0   1 220     Medium     0    80   190   15
2  Militia Human    -    -   1 220      Heavy     4   140   270    -
3  Footman Human  135    0   2 420      Heavy     2   140   270   20
4 Rifleman Human  205   30   3 535     Medium     0   140   270   26
5   Knight Human  245   60   4 835      Heavy     5   140   350   45
6   Priest Human  135   10   2 290  Unarmored     0   140   270   28
  Ground.Attack Damage Cooldown   DPS Range Air.Attack Damage.1 Cooldown.1
1        Normal   5.50     2.00  2.75     0          -        -          -
2        Normal  12.50     1.20 10.42     0          -        -          -
3        Normal  12.50     1.35  9.26     0          -        -          -
4        Pierce  21.00     1.50 14.00    40     Pierce    21.00       1.50
5        Normal  34.00     1.40 24.29     0          -        -          -
6         Magic   8.50     2.00  4.25    60      Magic     8.50       2.00
  DPS.1 Range.1
1     -       -
2     -       -
3     -       -
4 14.00      60
5     -       -
6  4.25      60

Посмотрим на данные

Удобно посмотреть, все ли прочитано корректно с помощью функции str()

str(df)
'data.frame':   71 obs. of  21 variables:
 $ Unit         : chr  "Peasant" "Militia" "Footman" "Rifleman" ...
 $ Race         : chr  "Human" "Human" "Human" "Human" ...
 $ Gold         : chr  "75" "-" "135" "205" ...
 $ Wood         : chr  "0" "-" "0" "30" ...
 $ Pop          : chr  "1" "1" "2" "3" ...
 $ HP           : int  220 220 420 535 835 290 325 600 200 360 ...
 $ Armor.Type   : chr  "Medium" "Heavy" "Heavy" "Medium" ...
 $ Armor        : chr  "0" "4" "2" "0" ...
 $ Sight        : int  80 140 140 140 140 140 140 140 180 140 ...
 $ Speed        : chr  "190" "270" "270" "270" ...
 $ Time         : chr  "15" "-" "20" "26" ...
 $ Ground.Attack: chr  "Normal" "Normal" "Normal" "Pierce" ...
 $ Damage       : chr  "5.50" "12.50" "12.50" "21.00" ...
 $ Cooldown     : chr  "2.00" "1.20" "1.35" "1.50" ...
 $ DPS          : chr  "2.75" "10.42" "9.26" "14.00" ...
 $ Range        : chr  "0" "0" "0" "40" ...
 $ Air.Attack   : chr  "-" "-" "-" "Pierce" ...
 $ Damage.1     : chr  "-" "-" "-" "21.00" ...
 $ Cooldown.1   : chr  "-" "-" "-" "1.50" ...
 $ DPS.1        : chr  "-" "-" "-" "14.00" ...
 $ Range.1      : chr  "-" "-" "-" "60" ...

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

Еще раз прочитаем, но уже правильно

Для корректного чтения используем аргумент na.strings, в который запишем как в нашей таблице обозначены пропущенные значения

df <- read.table('data/wc3_units.txt', sep = '\t', 
                 header = TRUE, na.strings = '-')
head(df)
str(df)
      Unit  Race Gold Wood Pop  HP Armor.Type Armor Sight Speed Time
1  Peasant Human   75    0   1 220     Medium     0    80   190   15
2  Militia Human   NA   NA   1 220      Heavy     4   140   270   NA
3  Footman Human  135    0   2 420      Heavy     2   140   270   20
4 Rifleman Human  205   30   3 535     Medium     0   140   270   26
5   Knight Human  245   60   4 835      Heavy     5   140   350   45
6   Priest Human  135   10   2 290  Unarmored     0   140   270   28
  Ground.Attack Damage Cooldown   DPS Range Air.Attack Damage.1 Cooldown.1
1        Normal    5.5     2.00  2.75     0       <NA>       NA         NA
2        Normal   12.5     1.20 10.42     0       <NA>       NA         NA
3        Normal   12.5     1.35  9.26     0       <NA>       NA         NA
4        Pierce   21.0     1.50 14.00    40     Pierce     21.0        1.5
5        Normal   34.0     1.40 24.29     0       <NA>       NA         NA
6         Magic    8.5     2.00  4.25    60      Magic      8.5        2.0
  DPS.1 Range.1
1    NA      NA
2    NA      NA
3    NA      NA
4 14.00      60
5    NA      NA
6  4.25      60
'data.frame':   71 obs. of  21 variables:
 $ Unit         : chr  "Peasant" "Militia" "Footman" "Rifleman" ...
 $ Race         : chr  "Human" "Human" "Human" "Human" ...
 $ Gold         : int  75 NA 135 205 245 135 155 215 90 180 ...
 $ Wood         : int  0 NA 0 30 60 10 20 30 30 70 ...
 $ Pop          : int  1 1 2 3 4 2 2 3 1 3 ...
 $ HP           : int  220 220 420 535 835 290 325 600 200 360 ...
 $ Armor.Type   : chr  "Medium" "Heavy" "Heavy" "Medium" ...
 $ Armor        : int  0 4 2 0 5 0 0 3 2 0 ...
 $ Sight        : int  80 140 140 140 140 140 140 140 180 140 ...
 $ Speed        : int  190 270 270 270 350 270 270 300 400 270 ...
 $ Time         : int  15 NA 20 26 45 28 30 28 13 40 ...
 $ Ground.Attack: chr  "Normal" "Normal" "Normal" "Pierce" ...
 $ Damage       : num  5.5 12.5 12.5 21 34 8.5 11 14 7.5 58 ...
 $ Cooldown     : num  2 1.2 1.35 1.5 1.4 2 1.75 1.9 2.5 3.5 ...
 $ DPS          : num  2.75 10.42 9.26 14 24.29 ...
 $ Range        : num  0 0 0 40 0 60 60 25 0 115 ...
 $ Air.Attack   : chr  NA NA NA "Pierce" ...
 $ Damage.1     : num  NA NA NA 21 NA 8.5 11 NA 14.5 NA ...
 $ Cooldown.1   : num  NA NA NA 1.5 NA 2 1.75 NA 2 NA ...
 $ DPS.1        : num  NA NA NA 14 NA 4.25 6.29 NA 7.25 NA ...
 $ Range.1      : num  NA NA NA 60 NA 60 60 NA 50 NA ...

Теперь пропущенные значения обработались корректно.

summary() - чтобы быстро оценить данные

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

summary(df)
     Unit               Race                Gold            Wood       
 Length:71          Length:71          Min.   : 60.0   Min.   :  0.00  
 Class :character   Class :character   1st Qu.:135.0   1st Qu.: 20.00  
 Mode  :character   Mode  :character   Median :190.0   Median : 30.00  
                                       Mean   :195.6   Mean   : 39.06  
                                       3rd Qu.:232.5   3rd Qu.: 60.00  
                                       Max.   :425.0   Max.   :120.00  
                                       NA's   :23      NA's   :23      
      Pop              HP          Armor.Type            Armor       
 Min.   :0.000   Min.   :  75.0   Length:71          Min.   : 0.000  
 1st Qu.:2.000   1st Qu.: 300.0   Class :character   1st Qu.: 0.000  
 Median :3.000   Median : 435.0   Mode  :character   Median : 1.000  
 Mean   :2.729   Mean   : 553.7                      Mean   : 1.257  
 3rd Qu.:4.000   3rd Qu.: 700.0                      3rd Qu.: 2.000  
 Max.   :7.000   Max.   :1600.0                      Max.   :10.000  
 NA's   :12                                          NA's   :1       
     Sight           Speed            Time      Ground.Attack     
 Min.   : 80.0   Min.   :190.0   Min.   :13.0   Length:71         
 1st Qu.:120.0   1st Qu.:270.0   1st Qu.:24.0   Class :character  
 Median :140.0   Median :270.0   Median :30.0   Mode  :character  
 Mean   :135.9   Mean   :283.1   Mean   :32.8                     
 3rd Qu.:140.0   3rd Qu.:320.0   3rd Qu.:40.0                     
 Max.   :190.0   Max.   :400.0   Max.   :65.0                     
                 NA's   :1       NA's   :22                       
     Damage          Cooldown          DPS            Range       
 Min.   :  5.50   Min.   :1.000   Min.   : 2.50   Min.   :  0.00  
 1st Qu.: 12.50   1st Qu.:1.500   1st Qu.: 7.89   1st Qu.:  0.00  
 Median : 19.50   Median :1.750   Median :11.85   Median : 30.00  
 Mean   : 26.68   Mean   :1.902   Mean   :14.04   Mean   : 33.36  
 3rd Qu.: 34.50   3rd Qu.:2.100   3rd Qu.:17.63   3rd Qu.: 51.25  
 Max.   :104.00   Max.   :4.500   Max.   :48.57   Max.   :115.00  
 NA's   :4        NA's   :3       NA's   :4       NA's   :3       
  Air.Attack           Damage.1       Cooldown.1        DPS.1       
 Length:71          Min.   : 7.50   Min.   :1.050   Min.   : 3.570  
 Class :character   1st Qu.:12.00   1st Qu.:1.500   1st Qu.: 7.415  
 Mode  :character   Median :18.00   Median :1.750   Median :11.140  
                    Mean   :25.59   Mean   :1.733   Mean   :15.510  
                    3rd Qu.:32.75   3rd Qu.:2.000   3rd Qu.:20.415  
                    Max.   :89.00   Max.   :3.000   Max.   :50.950  
                    NA's   :36      NA's   :36      NA's   :36      
    Range.1     
 Min.   : 0.00  
 1st Qu.:35.00  
 Median :50.00  
 Mean   :44.64  
 3rd Qu.:60.00  
 Max.   :60.00  
 NA's   :36     

Экспорт данных из R

Сохраним первые 15 строк датасета mtcars в файловую систему.

write.table(mtcars[1:5, ], 'mtcars.txt')

Спасибо за поддержку

Напоминаю, что лекции будут продолжаться при наличии поддержки слушателей, поскольку официально курс никак не финансируется.

Ссылка и куар код: