Загрузим данные из файла firtree.csv
, в котором хранятся результаты вымышленного опроса посетителей елочного базара, и сохраним их в переменную tree
.
Показатели в файле:
gender
– пол респондента;
ftype
– тип елки, которую оценивал респондент;
height
– высота елки в сантиметрах;
expenses
– сумма (в рублях), которую респондент готов отдать за елку;
score
– оценка, которую респондент поставил елке (1 – очень плохо, 5 – отлично);
wish
– ответ на вопрос «Хотели бы, чтобы Вам подарили такую елку?» (да, нет).
Так как в файле есть текст на кириллице и создавался он на Mac OS/Linux, необходимо добавить аргумент encoding
со значением кодировки "UTF-8"
, иначе на Windows вместо русских букв в тексте будут крокозябры:
tree <- read.csv("D:/Downloads/firtree.csv",
encoding = "UTF-8")
tree <- read.csv("/Users/allat/Desktop/firtree.csv")
Похожая проблема может возникнуть, если наоборот, файл с кириллицей создавался на Windows, а загружается в R на Mac/Linux. Тогда нужно будет выставить кодировку "Windows-1251"
.
Если добавление кодировки в encoding
не решает проблему (текст не отображается в читаемом виде), нужно перед работой с файлом запустить следующую строчку кода:
Sys.setlocale("LC_CTYPE", "ru_RU.UTF-8")
Этот код сохранит настройки языка и кодировки, и файлы на русском языке будут благополучно открываться.
Посмотрим на датафрейм — функция View()
открывает датафрейм в отдельной вкладке:
View(tree)
Внимание: первая буква у View()
заглавная!
Теперь запросим первые строки датафрейма:
head(tree)
## X gender ftype height score expenses wish
## 1 1 female пихта Нобилис 190 3 1051 да
## 2 2 male пихта Нобилис 174 3 2378 нет
## 3 3 female сосна Крым 248 4 655 да
## 4 4 female сосна Крым 191 1 2934 да
## 5 5 female сосна Крым 147 3 1198 нет
## 6 6 male сосна Крым 91 3 2139 да
По умолчанию функция head()
выдает первые 6 строк, но это можно изменить:
# первые 8 строк
head(tree, 8)
## X gender ftype height score expenses wish
## 1 1 female пихта Нобилис 190 3 1051 да
## 2 2 male пихта Нобилис 174 3 2378 нет
## 3 3 female сосна Крым 248 4 655 да
## 4 4 female сосна Крым 191 1 2934 да
## 5 5 female сосна Крым 147 3 1198 нет
## 6 6 male сосна Крым 91 3 2139 да
## 7 7 male ель обыкновенная 151 5 702 да
## 8 8 female ель обыкновенная 94 2 2707 нет
Аналогичным образом можно вывести последние строки датафрейма:
View(tail(tree))
Здесь функцию tail()
мы заключили в View()
, чтобы строки выводились в удобном формате — не в консоль, а в отдельном окне.
Для начала запросим размерность датафрейма: число строк и число столбцов.
# 1200 строк и 7 столбцов
dim(tree)
## [1] 1200 7
Функция dim()
возвращает вектор из двух элементов, причем на первом месте всегда идет число строк, на втором — число столбцов. Если нам нужно только число строк или только число столбцов, можно выбрать нужный элемент по индексу, а можно поступить проще — воспользоваться готовыми функциями.
Функция ncol()
возвращает число столбцов, а функция nrow()
— число строк.
ncol(tree)
## [1] 7
nrow(tree)
## [1] 1200
Если мы хотим получить техническое описание датафрейма — сколько в нем строк и столбцов, какого типа эти столбцы, можно воспользоваться функцией str()
. Эта функция (str
от structure) возвращает структуру любого объекта, не только датафрейма, поэтому, если не совсем ясно, какой объект выдала какая-нибудь функция из неизвестной библиотеки, можно смело ее использовать.
Посмотрим на структуру датафрейма tree
:
str(tree)
## 'data.frame': 1200 obs. of 7 variables:
## $ X : int 1 2 3 4 5 6 7 8 9 10 ...
## $ gender : Factor w/ 2 levels "female","male": 1 2 1 1 1 2 2 1 1 2 ...
## $ ftype : Factor w/ 4 levels "ель обыкновенная",..: 2 2 4 4 4 4 1 1 1 3 ...
## $ height : int 190 174 248 191 147 91 151 94 138 221 ...
## $ score : int 3 3 4 1 3 3 5 2 5 4 ...
## $ expenses: int 1051 2378 655 2934 1198 2139 702 2707 713 1521 ...
## $ wish : Factor w/ 3 levels "","да","нет": 2 3 2 2 3 2 2 3 3 3 ...
Со столбцами X
, height
, score
и expenses
все понятно, это обычные целочисленные столбцы типа integer. С остальными столбцами интереснее — они имеют тип factor
. Levels
здесь — это уникальные значения в векторе.
Тип factor
используется в тех случаях, когда нечисловые, качественные, значения кодируются числами. Другими словами, когда числа «ненастоящие», когда с ними нельзя работать как с числами в математике. Например, если вместо значений "female"
и "male"
в столбце gender
мы будем ставить 0 и 1, мы все равно не сможем говорить, что 1 здесь больше 0, это какие-то наши условные обозначения, результат договоренности. Или, например, если мы будем кодировать любимый цвет респондента числами от 1 до 4 (красный, желтый, зеленый, синий), мы не сможем сравнивать эти числа и утверждать, что 4 в два раза больше 2, потому что это то же самое, что сравнивать слова «желтый» и «синий». Считать среднее значение по такому набору чисел тоже неправильно, даже если технически мы можем все числа сложить и поделить на их количество, потому что результат будет неинтерпретируемым. Ведь непонятно, что такое средний цвет, равный, к примеру, 2.5.
Особого внимания заслуживает столбец wish
. Помимо очевидных значений "да"
и "нет"
здесь есть значение ""
. На самом деле это пустые ячейки, которые считались в R таким образом. Чтобы они нам не мешали, давайте еще раз загрузим файл, добавив опцию na.strings = ""
, которая принудит R считать такие ячейки за полноценные пропущенные значения NA
:
tree <- read.csv("D:/Downloads/firtree.csv",
encoding = "UTF-8", na.strings = "")
Посмотрим на структуру обновленного датафрейма:
str(tree)
## 'data.frame': 1200 obs. of 7 variables:
## $ X : int 1 2 3 4 5 6 7 8 9 10 ...
## $ gender : Factor w/ 2 levels "female","male": 1 2 1 1 1 2 2 1 1 2 ...
## $ ftype : Factor w/ 4 levels "ель обыкновенная",..: 2 2 4 4 4 4 1 1 1 3 ...
## $ height : int 190 174 248 191 147 91 151 94 138 221 ...
## $ score : int 3 3 4 1 3 3 5 2 5 4 ...
## $ expenses: int 1051 2378 655 2934 1198 2139 702 2707 713 1521 ...
## $ wish : Factor w/ 2 levels "да","нет": 1 2 1 1 2 1 1 2 2 2 ...
Все исправилось!
Теперь мы точно знаем, что в некоторых столбцах есть пропущенные значения (NA’s). Попробуем их посчитать. Для начала воспользуемся функцией complete.cases()
, которая вернет нам вектор из значений TRUE
и FALSE
, где TRUE
означает, что строка в таблице не содержит пропущенные значения (case — это строка, то есть одно наблюдение). Выведем первые несколько значений вектора:
head(complete.cases(tree))
## [1] TRUE TRUE TRUE TRUE TRUE TRUE
Теперь, чтобы посчитать число полностью заполненных строк, нам достаточно посчитать число TRUE
. Сделать это очень просто: R воспринимает значения TRUE
как 1, а FALSE
— как 0, поэтому можно просто суммировать все значения в векторе выше:
sum(complete.cases(tree))
## [1] 1198
Но нам нужен противоположный набор значений, ведь мы хотим посчитать число строк с пропущенными значениями! Поэтому к complete.cases()
нужно добавить отрицание. Отрицание в программировании обычно задаётся с помощью восклицательного знака. Поставим его перед функцией и получим «перевернутый» вектор, где TRUE
и FALSE
поменялись местами.
sum(!complete.cases(tree))
## [1] 2
Получается, в датафрейме tree
у нас есть две строки, в которых есть хотя бы одно пропущенное значение.
Важно: в R есть еще одна функция для поиска пропущенных значений — is.na()
:
sum(is.na(tree))
## [1] 2
В нашем случае результаты с complete.cases()
и is.na()
совпадают, но так будет не всегда. Функция complete.cases()
проверяет заполненность строк, а функция is.na()
— заполненность ячеек. Допустим, у нас есть маленький датафрейм такого вида:
test <- cbind.data.frame(a = c(NA, 2, 3),
b = c(NA, NA, 1))
test
## a b
## 1 NA NA
## 2 2 NA
## 3 3 1
В нем две строки, содержащие хотя бы один NA
, но всего пропущенных значений три. Сравним результаты:
sum(!complete.cases(test))
## [1] 2
sum(is.na(test))
## [1] 3
Выведем описательные статистики по всему датафрейму tree
с помощью функции summary()
:
summary(tree)
## X gender ftype height
## Min. : 1.0 female:612 ель обыкновенная:258 Min. : 70.0
## 1st Qu.: 300.8 male :588 пихта Нобилис :326 1st Qu.:115.0
## Median : 600.5 сосна датская :289 Median :157.0
## Mean : 600.5 сосна Крым :327 Mean :159.1
## 3rd Qu.: 900.2 3rd Qu.:203.2
## Max. :1200.0 Max. :250.0
## score expenses wish
## Min. :1.000 Min. : 302.0 да :611
## 1st Qu.:2.000 1st Qu.: 904.8 нет :587
## Median :3.000 Median :1630.5 NA's: 2
## Mean :3.005 Mean :1629.0
## 3rd Qu.:4.000 3rd Qu.:2300.0
## Max. :5.000 Max. :2999.0
Для количественных показателей функция возвращает минимальное и максимальное значения (Min.
и Max
), среднее арифметическое и медиану (Mean
и Median
), а также нижний и верхний квантили (1st Qu.
и 3rd Qu.
). Так, для столбца height
получаем:
Для качественных показателей функция возвращает частоты — сколько раз то или иное значение встречается в столбце. Количество пропущенных значений тоже учитывается.
Для дальнейшей работы с пропущенными значениями нам понадобятся дополнительные библиотеки. Установим их.
install.packages("VIM")
install.packages("mice")
Обратимся к ним:
library(VIM)
library(mice)
Выведем графики, которые покажут, в каких переменных пропущенных значений больше всего и как выглядит таблица с пропущенными значениями (паттерны пропущенных значений).
На графике слева показано, с какой частотой встречаются пропущенные значения в той или иной переменной. На графике справа показано, в каких комбинациях эти пропущенные значения встречаются.
# aggr - из библиотеки VIM
aggr(tree)
Следующий график отвечает за заполненность наблюдений (красным цветом отмечены пропущенные значения, остальное - заполненные значения, чем темнее цвет, тем больше значение). По вертикальной оси — номер строки в датафрейме, id наблюдения.
# matrixplot - из библиотеки mice
matrixplot(tree)
##
## Click in a column to sort by the corresponding variable.
## To regain use of the VIM GUI and the R console, click outside the plot region.
Так как в датафрейме tree
всего две строчки с пропущенными значениями и они не рядом, на графике их почти не видно. Но если пропусков много, этот график их покажет, сразу станет видно красные «дыры» на фоне серых и черных полосок. Для примера можем посмотреть на тот же график для test
:
matrixplot(test)
##
## Click in a column to sort by the corresponding variable.
## To regain use of the VIM GUI and the R console, click outside the plot region.
Датафрейм маленький, и по графику сразу видно, что ячеек с пропущенными значениями много. если сравнивать с общим числом ячеек в датафрейме.
Если нас интересует отдельный столбец датафрейма, его можно выбрать через $
:
head(tree$wish) # первые несколько значений
## [1] да нет да да нет да
## Levels: да нет
Выбрать, а дальше описывать отдельно. Если показатель качественный (текстовый или факторный), для него логично определить уникальные значения:
unique(tree$wish)
## [1] да нет <NA>
## Levels: да нет
И соответствующие им частоты:
table(tree$wish)
##
## да нет
## 611 587
Потом эту таблицу частот можно поместить внутрь функции barplot()
и построить столбиковую диаграмму:
barplot(table(tree$wish))
Можем добавить цвета:
barplot(table(tree$wish),
col = c("hotpink", "pink"))
График далек от идеального: подписей нет, вертикальная ось коротковата… Но настройкой графиков мы будем заниматься позже, пока просто смотрим, что возможность быстро построить график есть.
Уже знакомую нам функцию summary()
мы можем применить и к отдельному столбцу (и к вектору вне датафрейма тоже):
summary(tree$expenses)
## Min. 1st Qu. Median Mean 3rd Qu. Max.
## 302.0 904.8 1630.5 1629.0 2300.0 2999.0
Здесь уже все знакомо.
Теперь посмотрим на более подробную выдачу R с описательными статистиками. Чтобы это сделать, нам понадобится библиотека psych
, которая содержит набор функций, часто используемых в психометрических исследованиях. Установим ее:
install.packages("psych")
Обратимся к библиотеке через library()
:
library(psych)
Теперь запросим описательные статистики для столбца expenses
с помощью функции describe()
:
describe(tree$expenses)
## vars n mean sd median trimmed mad min max range skew kurtosis
## X1 1 1200 1629.05 792.82 1630.5 1622.58 1028.18 302 2999 2697 0.06 -1.24
## se
## X1 22.89
Что есть что?
vars
: число описываемых переменных (не путать с var для дисперсии);n
: число наблюдений;mean
: среднее арифметическое, выборочное среднее;sd
: стандартное отклонение;median
: медиана;trimmed
: усеченное среднее, среднее по цензурированной выборке (см. ниже);mad
: медианное значение абсолютного отклонения от медианы (нам не понадобится);min
, max
: минимальное и максимальное значение;range
: размах;skew
: коэффициент асимметрии или скошенности (см.нижк);kurtosis
: коэффициент эксцесса (см. ниже);se
: стандартная ошибка среднего;Подробнее про некоторые статистики.
Усеченное среднее, среднее по цензурированной выборке
Считается так: выборка упорядочивается по возрастанию, из неё убирается 5% наблюдений слева и справа (наименьшие и наибольшие), потом по такой усеченной или цензурированной выборке считается обычное среднее арифметическое.
Наравне с медианой считается более устойчивой оценкой среднего, так как после усечения выборки такой показатель уже несильно зависит от слишком больших или слишном маленьких (нетипичных) значений в выборке. То есть, при наличии нетипичных наблюдений в выборке (выбросов) такое среднее более адекватно отражает реальность, чем обычное среднее арифметическое.
Коэффициент асимметрии
Показатель принимает значения примерно от -3 до 3. Значение 0 соответствует симметричному распределению (например, нормальному, вспомните график плотности, симметричный относительно математического ожидания). Значения меньше 0 соответствуют распределению, которое скошено влево (длинный хвост «слева»), значения больше 0 соответствуют распределению, которое скошено вправо (длинный «хвост» справа).
В нашем случае распределение почти симметричное, коэффициент близок к нулю, но при это оно немного скошено вправо, поэтому значение больше 0.
Коэффициент эксцесса
Показатель принимает значения примерно от -3 до 3 и отвечает за выраженность пика распределения. Чем больше значение коэффициента, тем более выраженный пик. Стандартное нормальное распределение имеет коэффициент эксцесса равный 0. Отрицательные значения коэффициента соответствуют более «плоским» и «гладким» распределениям, у которых пик не такой заметный. Посмотрите на картинку здесь и сравните.
В нашем случае распределение несильно отличается от нормального, поэтому коэффициент близок к нулю.
Библиотека psych
удобна тем, что она содержит функцию describeBy()
, которая позволяет выводить описательные статистики по группам. Нет необходимости отфильтровывать нужные строки и сохранять их в отдельные датасеты, можно просто указать группирующую переменную. Например, сравним, сколько на хвойные деревья могут тратить мужчины и женщины:
describeBy(tree$expenses, tree$gender)
##
## Descriptive statistics by group
## group: female
## vars n mean sd median trimmed mad min max range skew kurtosis
## X1 1 612 1640.38 785.23 1652.5 1635.9 1012.62 302 2999 2697 0.03 -1.25
## se
## X1 31.74
## ------------------------------------------------------------
## group: male
## vars n mean sd median trimmed mad min max range skew kurtosis
## X1 1 588 1617.25 801.15 1597 1608.73 1052.65 302 2992 2690 0.09 -1.24
## se
## X1 33.04
Очень удобно!
Если нас интересует только определенная характеристика столбца, можем воспользоваться базовыми, уже знакомыми нам, функциями.
min(tree$expenses) # минимум
## [1] 302
max(tree$expenses) # максимум
## [1] 2999
mean(tree$expenses) # среднее
## [1] 1629.045
median(tree$expenses) # медиана
## [1] 1630.5
var(tree$expenses) # дисперсия
## [1] 628562.6
sd(tree$expenses) # стандартное отклонение
## [1] 792.8194
Однако у всех этих функций есть одна особенность — они возвращают NA
, если в столбце или векторе есть хотя бы одно пропущенное значение. Попробуем посчитать среднее для вектора с NA
:
mean(c(7, 5, NA, 9))
## [1] NA
Нет ответа, плюс, получили предупреждение о наличие NA
. Чтобы этого избежать, можно добавить опцию na.rm = TRUE
, сокращение от NA remove:
mean(c(7, 5, NA, 9), na.rm = TRUE)
## [1] 7
Пропущенные значения не удаляются из самого вектора, но не учитываются при вычислении среднего. То же будет актуально и для других характеристик (минимум, медиана и прочие).
Напоследок построим гистограмму:
hist(tree$expenses)
Добавим цвет:
hist(tree$expenses, col = "hotpink")
Выбор цветов в R богатый, список всех цветов с примерами можно посмотреть здесь. При желании можно вводить не название цвета, а его код в формате RGB или HEX. Пример с цветом в формате HEX (hexadecimal):
hist(tree$expenses, col = "#266136")
Про форматы цветов можно посмотреть здесь.
Настройку графиков и наведение красоты мы обсудим позже.