Загрузим файл с результатами опроса, который вы проходили в прошлый раз (данные пока неполные, преимущественно группы по R). Файл survey01.csv
можно скачать на странице курса. Формат .csv
, сокращение от comma separated values - довольно распространённый формат хранения данных, это текстовый файл, в котором столбцы отделены друг от друга запятыми.
Для загрузки csv-файла в R используется функция read.csv()
. В скобках внутри этой функции можно указать название файла в кавычках, ссылку на файл в кавычках или прописать специальную функцию, которая позволит выбрать файл с компьютера. Давайте начнём с последнего способа и напишем file.choose()
, функцию, которая сообщит R, что нужно открыть окно для выбора файла.
dat <- read.csv(file.choose())
После исполнения этой строчки кода появится окно для выбора файла, нужно найти файл на компьютере, выбрать его и нажать Открыть. Если файл загрузился успешно, ничего особенного в консоли мы не увидим, но во вкладке Environment появится объект dat
.
Примечание: иногда на Windows окно для выбора файла появляется, но не открывается явно, а висит в свернутом виде внизу, на панели задач. Если окна не видно, посмотрите вниз и разверните его.
Теперь посмотрим на сводную информацию по загруженной таблице.
str(dat)
## 'data.frame': 108 obs. of 9 variables:
## $ height : Factor w/ 37 levels "145","150","151",..: 10 8 28 35 20 24 17 31 22 7 ...
## $ math : int 70 72 72 62 86 NA 70 68 82 91 ...
## $ bio : int 90 66 96 74 NA NA 65 88 80 90 ...
## $ subject : int 4 1 2 2 NA 2 1 2 1 1 ...
## $ gender : int 1 1 2 2 2 1 1 1 1 1 ...
## $ residence: int 2 1 1 2 2 2 1 2 1 1 ...
## $ length : Factor w/ 25 levels "","0","12","14",..: 1 22 14 8 11 4 14 8 14 25 ...
## $ angle : Factor w/ 24 levels "0","11","12",..: 12 16 11 4 18 8 4 11 16 7 ...
## $ soft : Factor w/ 2 levels "R","SPSS": 1 1 1 1 1 1 1 1 1 1 ...
В таблице 108 наблюдений (строк) и 11 переменных (столбцов).
Переменные:
height
: рост студента (в см);math
: балл за ЕГЭ по математике;bio
: балл за ЕГЭ по биологии;subject
: любимый предмет в школе (последовательность как в анкете);gender
: пол (1 - женский, 2 - мужской);residence
: место постоянного проживания (1 - Москва, 2 - не Москва);length
: оценка студентом длины отрезка (в см);angle
: оценка студентом величины угла (в градусах);soft
: группа (R или SPSS).Если посмотреть на типы столбцов, мы заметим одну странность: даже те столбцы, которые должны быть числовыми (рост, число баллов за ЕГЭ по математике и биологии, длина отрезка и величина угла) считались как текстовые, chr или character. Далее работать с этим показателями как с числовыми не получится! Почему это произошло? Это случилось потому, что в R десятичным разделителем по умолчанию считается точка, а в нашем файле, который экспортировался из Excel или Google Tables, в дробных числах используется запятая. Чтобы это исправить, достаточно дописать специальную опцию dec
(от decimal separator) и сообщить R, что в данном файле запятые надо воспринимать не как символ в тексте, а как разделитель в числах.
dat <- read.csv(file.choose(), dec = ",")
Теперь всё должно быть, как нужно:
str(dat)
## 'data.frame': 108 obs. of 9 variables:
## $ height : num 162 160 178 186 170 ...
## $ math : int 70 72 72 62 86 NA 70 68 82 91 ...
## $ bio : int 90 66 96 74 NA NA 65 88 80 90 ...
## $ subject : int 4 1 2 2 NA 2 1 2 1 1 ...
## $ gender : int 1 1 2 2 2 1 1 1 1 1 ...
## $ residence: int 2 1 1 2 2 2 1 2 1 1 ...
## $ length : num NA 40 25 20 22.5 14 25 20 25 50 ...
## $ angle : num 26 30 25 15 35 22 15 25 30 20 ...
## $ soft : Factor w/ 2 levels "R","SPSS": 1 1 1 1 1 1 1 1 1 1 ...
Всё сработало. Конечно, для идеального состояния датасета нам нужно было бы преобразовать столбцы gender
, residence
, subject
в факторные, чтобы R понимал, что с ними нельзя обращаться как с числами. Но нам пока это мешать не будет, давайте оставим всё, как есть. Посмотрим на описательные статистики по каждому столбцу в таблице:
summary(dat)
## height math bio subject
## Min. :145.0 Min. :50.00 Min. : 60.00 Min. :1.000
## 1st Qu.:163.0 1st Qu.:70.00 1st Qu.: 70.50 1st Qu.:2.000
## Median :168.8 Median :74.00 Median : 79.00 Median :2.000
## Mean :168.8 Mean :74.11 Mean : 80.17 Mean :2.689
## 3rd Qu.:174.2 3rd Qu.:79.50 3rd Qu.: 89.75 3rd Qu.:4.000
## Max. :190.0 Max. :99.00 Max. :100.00 Max. :5.000
## NA's :13 NA's :18 NA's :2
## gender residence length angle
## Min. : 1.000 Min. :1.000 Min. : 0.00 Min. : 0.00
## 1st Qu.: 1.000 1st Qu.:1.000 1st Qu.:20.00 1st Qu.:21.50
## Median : 1.000 Median :1.000 Median :25.00 Median :25.00
## Mean : 1.402 Mean :1.472 Mean :23.86 Mean :26.53
## 3rd Qu.: 1.000 3rd Qu.:2.000 3rd Qu.:27.00 3rd Qu.:30.00
## Max. :22.000 Max. :3.000 Max. :50.00 Max. :60.00
## NA's :1 NA's :2 NA's :1
## soft
## R :93
## SPSS:15
##
##
##
##
##
Видно, что в некоторых столбцах есть пропущенные значения (NA's
). Попробуем их посчитать. Для начала воспользуемся функцией complete.cases()
, которая вернёт нам вектор из значений TRUE
и FALSE
, где TRUE
означает, что строка в таблице не содержит пропущенные значения (case - это строка, то есть одно наблюдение). Проверим:
complete.cases(dat)
## [1] FALSE TRUE TRUE TRUE FALSE FALSE TRUE TRUE TRUE TRUE TRUE
## [12] TRUE FALSE TRUE FALSE TRUE FALSE TRUE FALSE FALSE TRUE TRUE
## [23] TRUE TRUE TRUE TRUE FALSE TRUE FALSE TRUE TRUE FALSE FALSE
## [34] FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [45] TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE
## [56] TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE TRUE TRUE TRUE
## [67] TRUE FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
## [78] TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE
## [89] FALSE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE FALSE
## [100] TRUE TRUE FALSE FALSE FALSE TRUE TRUE FALSE TRUE
Видно, что у первого респондента есть пропущенные значения, он ответил не на все вопросы (FALSE
), у второго ситуация обратная - все значения заполнены (TRUE
). Но нам нужен противоположный набор значений, ведь мы хотим посчитать число строк с пропущенными значениями! Поэтому к complete.cases()
нужно добавить отрицание. Отрицание в программировании обычно задаётся с помощью восклицательного знака. Поставим его перед функцией и получим «перевёрнутый» вектор, где TRUE
и FALSE
поменялись местами.
!complete.cases(dat)
## [1] TRUE FALSE FALSE FALSE TRUE TRUE FALSE FALSE FALSE FALSE FALSE
## [12] FALSE TRUE FALSE TRUE FALSE TRUE FALSE TRUE TRUE FALSE FALSE
## [23] FALSE FALSE FALSE FALSE TRUE FALSE TRUE FALSE FALSE TRUE TRUE
## [34] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [45] FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
## [56] FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE FALSE FALSE FALSE
## [67] FALSE TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE
## [78] FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
## [89] TRUE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE FALSE TRUE
## [100] FALSE FALSE TRUE TRUE TRUE FALSE FALSE TRUE FALSE
Теперь, чтобы посчитать число строк с пропущенными значениями, нам достаточно посчитать число TRUE
. Сделать это очень просто: R воспринимает значения TRUE
как 1, а FALSE
- как 0, поэтому можно просто суммировать все значения в векторе выше:
sum(!complete.cases(dat))
## [1] 23
Получается, в нашем датасете у нас есть 23 строки с пропущенными значениями. Так как заполнение пропущенных значений - отдельная серьёзная тема, давайте просто удалим строки с пропущенными значениями.
dat <- na.omit(dat) # удаляем
Обратите внимание: число наблюдений в датасете, которое видно рядом с dat
в Environment, должно измениться и стать 85 (108 - 23 = 85).
Чтобы дальнейшие манипуляции со строками не казались чем-то магическим, давайте обсудим общую логику выбора элементов датафрейма в R. Выбор элементов осуществляется с помощью квадратных скобок, которые ставятся после названия датафрейма. В этих квадратных скобках можно указывать либо номера/номера столбцов и строк, которые нас интересуют, либо условия, в соответствии с которыми будут отбираться элементы. На первом месте указывается номер или условие для строки, на втором месте - для столбца.
Для примера запросим элемент датафрейма dat
, который находится на пересечении первой строки и второго столбца:
dat[1, 2]
## [1] 72
Если нас интересуют все строки или столбцы, то какой-то из номеров, до запятой или после запятой, можно опустить:
# первая строка и все столбцы, все данные по 1-ому студенту
dat[1,]
## height math bio subject gender residence length angle soft
## 2 160 72 66 1 1 1 40 30 R
# все строки и второй столбец, значения math для всех студентов
dat[,2]
## [1] 72 72 62 70 68 82 91 76 72 86 72 78 67 76 68 80 80 78 79 74 68 72 65
## [24] 76 70 72 70 50 72 61 72 76 74 80 72 72 74 84 82 94 85 72 50 74 74 72
## [47] 70 74 70 74 80 78 70 59 74 70 50 80 74 70 70 77 76 62 99 78 78 75 70
## [70] 56 78 80 80 78 76 56 78 62 56 82 84 75 76 88 62
Теперь перейдём к более важному: посмотрим, как можно выбирать строки по условиям. Для начала перечислим операторы, которые используются в R для формулировки условий:
>
: больше;<
: меньше;>=
: больше или равно;<=
: меньше или равно;==
: равно (обязательно двойное, чтобы отличалось от присваивания значений через =
);!=
: не равно (отрицание через !
).Выберем те строки из dat
, которые соответствуют студентам, набравшим более 80 баллов по математике. В квадратных скобках на первом месте укажем условие dat$math > 80
, а на втором, после запятой, ничего писать не будем - столбцы нас интересуют все:
dat[dat$math > 80, ]
## height math bio subject gender residence length angle soft
## 9 172.0 82 80 1 1 1 25 30 R
## 10 158.0 91 90 1 1 1 50 20 R
## 14 157.5 86 92 5 1 1 20 23 R
## 51 160.0 84 60 1 1 1 24 38 R
## 53 174.0 82 88 2 1 1 25 36 R
## 54 165.0 94 78 1 1 2 24 23 R
## 55 169.0 85 80 1 1 2 22 17 R
## 81 145.0 99 100 4 3 1 25 20 R
## 98 167.0 82 82 1 1 2 27 30 R
## 100 164.0 84 74 3 1 1 21 25 R
## 106 181.0 88 68 1 1 2 25 30 R
Обратите внимание: запятая после условия важна! Если её не поставить, R будет выдавать ошибку undefined columns selected
, то есть не понимать, какие столбцы при этом мы хотим выбрать.
Выберем строки, которые соответствуют студентам, набравшим не менее 80 баллов по математике:
dat[dat$math >= 80, ]
## height math bio subject gender residence length angle soft
## 9 172.0 82 80 1 1 1 25 30 R
## 10 158.0 91 90 1 1 1 50 20 R
## 14 157.5 86 92 5 1 1 20 23 R
## 24 168.0 80 92 5 1 1 23 25 SPSS
## 25 186.0 80 92 5 2 2 25 25 SPSS
## 47 164.0 80 94 2 1 2 18 11 R
## 51 160.0 84 60 1 1 1 24 38 R
## 53 174.0 82 88 2 1 1 25 36 R
## 54 165.0 94 78 1 1 2 24 23 R
## 55 169.0 85 80 1 1 2 22 17 R
## 66 183.0 80 94 2 2 2 25 22 R
## 74 165.0 80 88 4 1 2 14 20 R
## 81 145.0 99 100 4 3 1 25 20 R
## 90 170.0 80 80 5 1 1 25 25 R
## 91 170.0 80 92 4 1 2 20 30 R
## 98 167.0 82 82 1 1 2 27 30 R
## 100 164.0 84 74 3 1 1 21 25 R
## 106 181.0 88 68 1 1 2 25 30 R
И ровно 80 баллов по математике:
dat[dat$math == 80, ]
## height math bio subject gender residence length angle soft
## 24 168 80 92 5 1 1 23 25 SPSS
## 25 186 80 92 5 2 2 25 25 SPSS
## 47 164 80 94 2 1 2 18 11 R
## 66 183 80 94 2 2 2 25 22 R
## 74 165 80 88 4 1 2 14 20 R
## 90 170 80 80 5 1 1 25 25 R
## 91 170 80 92 4 1 2 20 30 R
Можем сохранить интересующие нас строки в отдельный датафрейм и посмотреть на него в удобном режиме просмотра:
ne80 <- dat[dat$math != 80, ]
View(ne80)
Теперь посмотрим на то, как условия можно сочетать. В R сложные условия (из нескольких частей) задаются с помощью следующих операторов:
&
: одновременное выполнение условий (вспомните пересечение событий \(A\cap B\));|
: хотя бы одно из условий верно (или первое, или второе, или оба сразу, вспомните объединение событий \(A\cup B\)) .Выведем на экран строки, которые соответствуют студентам, набравших одновременно более 80 баллов по математике и по биологии:
dat[dat$math > 80 & dat$bio > 80, ]
## height math bio subject gender residence length angle soft
## 10 158.0 91 90 1 1 1 50 20 R
## 14 157.5 86 92 5 1 1 20 23 R
## 53 174.0 82 88 2 1 1 25 36 R
## 81 145.0 99 100 4 3 1 25 20 R
## 98 167.0 82 82 1 1 2 27 30 R
Посчитаем заодно, сколько таких студентов, воспользуемся функцией nrow()
, сокращение от number of rows, число строк:
nrow(dat[dat$math > 80 & dat$bio > 80, ])
## [1] 5
Теперь сделаем что-то более содержательное: уберём строки с невалидными значениями и сохраним изменения в исходном датафрейме dat
. Какие значения считать невалидными? Те, которые явно невозможны, результат опечаток или намеренного искажения ответа, иными словами, «мусор» в данных. Регион проживания равный 3, третий пол, значения длины отрезка и величины угла равные 0 (не мог человек оценить длинный отрезок в 0, видимо, это отказ отвечать). Плюс, на семинаре нас насторожили ответы человека с очень высокими баллами по математике и биологии. Уберём все эти «плохие» строки, а точнее, как получается по логике R, оставим только «хорошие»:
dat <- dat[dat$math != 99 & dat$bio != 100,]
dat <- dat[dat$residence == 1 | dat$residence == 2,] # оставляем либо 1, либо 2
dat <- dat[dat$gender < 3,] # оставляем пол менее 3
dat <- dat[dat$length != 0 | dat$angle != 0,] # оставляем ненулевые значения
Со строками мы уже поработали достаточно. Теперь посмотрим, как можно добавлять в датафрейм новые столбцы (например, на основе старых). Создать новый столбец довольно просто: нужно вписать его название после $
после названия самого датафрейма, а затем присвоить через <-
ему некоторое значение.
Для примера создадим столбец ege_sum
, который будет содержать сумму баллов по двум экзаменам:
dat$ege_sum <- dat$math + dat$bio # суммируем столбцы и присваиваем
Обратите внимание: все действия со столбцами выполняются поэлементно. То есть, когда мы пишем dat$math + dat$bio
, R понимает, что надо взять первый элемент из math
, первый элемент из bio
, сложить их, а затем проделать это для каждой пары значений в этих столбцах.
Напоследок создадим два столбца, которые нам понадобятся для практикума. Эти столбцы будут показывать, насколько студент отклонился от правильного ответа на вопросы про длину отрезка и величину угла, причём будем учитывать сразу отклонения как в большую сторону, так и в меньшую - посчитаем абсолютную разницу, по модулю.
Итак, истинная длина отрезка 22 см, величина угла 18 градусов, поехали:
dat$len_dev <- abs(22 - dat$length)
dat$ang_dev <- abs(18 - dat$angle)
Мы завершили предварительную обработку данных, перейдём к практикуму.