Загрузка данных и проверка типов переменных

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

Переменные:

Если посмотреть на типы столбцов, мы заметим одну странность: даже те столбцы, которые должны быть числовыми (рост, число баллов за ЕГЭ по математике и биологии, длина отрезка и величина угла) считались как текстовые, 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 сложные условия (из нескольких частей) задаются с помощью следующих операторов:

Выведем на экран строки, которые соответствуют студентам, набравших одновременно более 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)

Мы завершили предварительную обработку данных, перейдём к практикуму.