Загрузим файл с результатами опроса, который вы проходили в прошлый раз (данные пока неполные, преимущественно группы по 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)
Мы завершили предварительную обработку данных, перейдём к практикуму.