Изучив основные объекты в R, мы можем перейти к объектам, ради которых многие и начинают изучать R, а именно, к базам данных или датафреймам (точнее, к таблицам, просто в социально-экономических науках эти термины отождествляют, хотя, строго говоря, база данных - это набор связанных между собой таблиц). Но прежде необходимо научиться загружать файлы с данными, чтобы было с чем работать.

Работа с файлами

Загрузка данных в R

Напоминание. Если мы не хотим прописывать слишком длинный путь к файлу, файл с данными можно сохранить сразу в рабочую папку (папку, из которой запускается R). Тогда при попытке открыть файл с заданным названием R будет искать его в этой папке. Узнать, какая папка является рабочей, можно с помощью функции getwd():

getwd() # wd - от working directory
## [1] "/Users/allat/Desktop"

Рабочую папку можно изменить. Например, так:

setwd("C:/AllaT/Рабочий стол/")

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

csv-файлы

Формат csv (от comma separated values) - широко распространенный текстовый формат, который используется для представления табличных данных. В качестве разделителя, т.е. символа, который разделяет значения колонок, обычно используется запятая, как и следует из названия.

df <- read.csv("example1.csv")
View(df) # посмотреть на базу данных, V - заглавная

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

df1 <- read.csv("example2.csv")
View(df1)

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

df1 <- read.csv("example2.csv", sep = ";") # sep - от separator
View(df1)

Если в файле есть текст на кириллице, могут возникнуть проблемы при чтении файла или при его отображении. Решения могут быть разными (зависит от системы, ее параметров и самого файла). Самое простое - специфицировать кодировку самого файла:

df2 <- read.csv("example3.csv", encoding = "UTF-8")
df2 <- read.csv("example4.csv", encoding = "WINDOWS-1251")

Обычно текст в кодировке UTF-8 встречается в файлах, созданных в Mac OS или Linux, а WINDOWS-1251 (или CP-1251) - в Windows.

Будем считать, что с csv-файлами разобрались.

txt-файлы

При работе с txt-файлами необходимо указывать, каким образом столбцы отделены друг от друга (аргумент sep, разделитель, как и в случае в csv-файлами), а также учитывать, что представляет собой первая строка: наблюдение или шапку таблицы (аргумент header). Откроем файл, в котором столбы разделены табуляцией и сравним, как он будет выглядеть при выставлении разных значений параметра header:

# header = T - первая строка читается как имя переменной
table1 <- read.table('example1.txt', sep='\t', header = TRUE)  
View(table1) 
# header = F - первая строка читается как наблюдение
table2 <- read.table('example1.txt', sep='\t', header = FALSE)
View(table2) 

Теперь перейдем к другим форматам.

файлы Excel

Чтобы спокойно загружать xlsx-файлы в R можно установить специальные библиотеки: xlsx или readxl. С установкой первой библиотеки могут возникнуть проблемы: R будет писать что-то про rjava. Это обычно бывает, если на компьютере не установлена Java или установлена такая ее версия, которая конфликтует с R (например, недостаточно новая). Тогда Java можно поставить, скачав отсюда. После этого проблема должна исчезнуть, по крайней мере, на Windows.

install.packages("xlsx")

Теперь обратимся к этой библиотеке - иначе открыть файл мы не сможем:

library(xlsx)

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

ex_data <- read.xlsx("example1.xlsx", 1)

Иногда даже с переустановленной Java библиотека xlsx не хочет загружаться. Тогда можно пойти другим путем: в правом верхнем углу RStudio во вкладке Environment выбрать Import Dataset-From Excel, выбрать файл на компьютере через Browse, вписать название переменной, в которую сохраняем таблицу в R (например, df) в поле Name и нажать Import. Если в файле Excel более одного листа, тогда в поле Sheet нужно указать номер нужного листа.

Код, который исполняется, когда мы выполняем эти действия вручную, выглядит следующим образом:

library(readxl)
df_ex <- read_excel("example1.xlsx")

То есть, можно установить библиотеку readxl (с ней не будет таких проблем с Java), обратиться к ней и спокойно использовать по аналогии с кодом выше.

файлы STATA

Для загрузки файлов STATA (файлы с расширением .dta) потребуется библиотека foreign.

install.packages("foreign")
library(foreign)

Теперь загрузим dta-файл.

stata_data <- read.dta("example1.dta")

файлы SPSS

Для загрузки файлов SPSS (файлы с расширением .sav) потребуется библиотека Hmisc.

install.packages("Hmisc")
library(Hmisc)

Загрузим sav-файл.

sav_data <- spss.get("example1.sav")

Сохранение (экспорт) файлов

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

write.csv(df, "new_file.csv")

Работа с таблицами

Описание таблицы

Загрузим более содержательную таблицу. Таблицу, взятую с сайта IBM, содержащую данные об эффективности рекламных кампаний. Файл и codebook к ней можно найти здесь.

dat <- read.csv("http://math-info.hse.ru/f/2018-19/comm-math/marketing.csv")

Какую информацию о таблице мы можем получить?

Можем определить число наблюдений и число переменных в датафрейме. Узнать это можно точно так же, как и размерность матрицы, ведь число строк - это число наблюдений, а число столбцов - это число переменных.

dim(dat)
## [1] 548   7

Или по отдельности:

nrow(dat) # число строк
## [1] 548
ncol(dat) # число столбцов
## [1] 7

Можем узнать гораздо больше - структуру датафрейма: число наблюдений и переменных, типы переменных и примеры значений, которые они принимают. Сделать это можно с помощью уже знакомой функции str():

str(dat)
## 'data.frame':    548 obs. of  7 variables:
##  $ MarketID        : int  1 1 1 1 1 1 1 1 1 1 ...
##  $ MarketSize      : Factor w/ 4 levels "","Large","Medium",..: 3 3 3 3 3 3 3 3 3 3 ...
##  $ LocationID      : int  1 1 1 1 2 2 2 2 3 3 ...
##  $ AgeOfStore      : int  4 4 4 4 5 5 5 5 12 12 ...
##  $ Promotion       : int  3 3 3 3 2 2 2 2 1 1 ...
##  $ Week            : int  1 2 3 4 1 2 3 4 1 2 ...
##  $ SalesInThousands: num  33.7 35.7 29 39.2 27.8 ...

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

head(dat)
##   MarketID MarketSize LocationID AgeOfStore Promotion Week
## 1        1     Medium          1          4         3    1
## 2        1     Medium          1          4         3    2
## 3        1     Medium          1          4         3    3
## 4        1     Medium          1          4         3    4
## 5        1     Medium          2          5         2    1
## 6        1     Medium          2          5         2    2
##   SalesInThousands
## 1            33.73
## 2            35.67
## 3            29.03
## 4            39.25
## 5            27.81
## 6            34.67

Или последние:

tail(dat)
##     MarketID MarketSize LocationID AgeOfStore Promotion Week
## 543       10      Large        919          2         1    3
## 544       10      Large        919          2         1    4
## 545       10      Large        920         14         2    1
## 546       10      Large        920         14         2    2
## 547       10      Large        920         14         2    3
## 548       10      Large        920         14         2    4
##     SalesInThousands
## 543            57.20
## 544            64.34
## 545            50.20
## 546            45.75
## 547            44.29
## 548            49.41

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

Пропущенные значения

Результаты, которые выдают нам функции str() и dim(), содержат только общую информацию о количестве наблюдений в таблице и не дают никакой информации о пропущенных значениях. Как это исправить? Для начала можно выяснить, сколько в базе неполностью заполненных строк. Функция complete.cases() выдает логический вектор, где TRUE означает полностью заполненную строку, а FALSE - содержащую пропуски (NAs).

head(complete.cases(dat)) # head - первые несколько значений
## [1] TRUE TRUE TRUE TRUE TRUE TRUE

Посчитаем, сколько полностью заполненных наблюдений:

sum(complete.cases(dat))
## [1] 529

Соответственно, остальные (из 548) - недозаполненные (содержащие NAs).

Посмотрим на незаполненные строки:

View(dat[!complete.cases(dat), ]) # ! отрицание

И посчитаем их количество:

nrow(dat[!complete.cases(dat), ])
## [1] 19

Для дальнейшей работы с пропущенными значениями нам понадобятся дополнительные библиотеки. Установим их. Можно устанавливать сразу несколько библиотек – оформить перечень необходимых библиотек в виде вектора, и тогда сразу после установки одной библиотеки начнется загрузка следующей.

install.packages(c("mice", "VIM"))

Обратимся к ним:

library(mice)
library(VIM)

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

На графике слева показано, с какой частотой встречаются пропущенные значения в той или иной переменной. На графике справа показано, в каких комбинациях эти пропущенные значения встречаются. Например, в нашем случае отсутствие ответов в AgeOfStore часто совпадает с отсутствием ответов в SalesInThousands (пропущенные значения отмечены красным цветом).

# aggr - из библиотеки mice
aggr(dat)

Следующий график отвечает за заполненность наблюдений (красным цветом отмечены пропущенные значения, остальное - заполненные значения, чем темнее цвет, тем больше значение). По вертикальной оси - номер строки в базе данных (id наблюдения).

# matrixplot - из библиотеки VIM
matrixplot(dat) 

## 
## 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.

Удаление пропущенных значений

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

# удаляем строки, содержащие NA
dat <- na.omit(dat)

Выбор переменных

Если мы хотим обратиться к конкретной переменной и рассматривать ее как вектор элементов, нужно использовать символ $.

dat$Promotion # номер рекламной кампании
##   [1] 3 3 3 3 2 2 2 2 1 1 1 2 2 2 2 2 2 3 3 3 3 1 1 1 1 2 2 2 2 1 1 1 1 2 2
##  [36] 2 2 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3
##  [71] 3 3 3 3 3 3 1 1 1 1 3 3 3 3 2 2 2 2 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 1 1
## [106] 1 1 1 1 1 1 2 2 2 2 3 3 3 2 2 2 2 1 1 1 1 2 2 2 2 3 3 3 3 3 3 3 1 1 1
## [141] 1 2 2 2 2 1 1 1 1 2 2 2 1 1 1 1 2 2 2 3 3 3 1 1 1 1 1 1 1 2 2 2 1 1 1
## [176] 1 2 2 2 2 2 2 2 1 1 1 1 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 2 3
## [211] 3 3 3 2 2 2 2 2 2 2 2 1 1 1 1 3 3 3 3 1 1 1 2 2 2 2 3 3 3 3 3 3 3 3 3
## [246] 3 2 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 1 1 1 1 2 2 2
## [281] 2 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 3 3 3 3 2 2 2 2 3 3
## [316] 3 3 3 3 3 3 3 3 3 3 2 2 2 2 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3 1
## [351] 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 1 1 1 1 1 1 1 1 1 1 1 1 3 3 3 3 3 3 3 3
## [386] 1 1 1 1 2 2 2 2 2 2 2 2 3 3 3 3 1 1 1 1 3 3 3 3 1 1 1 1 2 2 2 2 3 3 3
## [421] 3 1 1 1 1 3 3 3 3 3 3 3 3 2 2 2 2 3 3 3 3 3 3 3 3 1 1 1 1 2 2 2 2 1 1
## [456] 1 1 3 3 3 3 2 2 2 2 3 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 2
## [491] 2 2 2 2 2 2 2 1 1 1 1 2 2 2 2 1 1 1 1 2 2 2 2 3 3 3 3 1 1 1 1 1 1 1 1
## [526] 2 2 2 2

Attach и detach

Мы можем «закрепить» базу данных с помощью команды attach, чтобы обращаться к переменным более простым способом:

attach(dat) # заодно покажет, какие переменные есть
head(Promotion) # имя переменной как есть, без $
## [1] 3 3 3 3 2 2

Однако это не всегда удобно, особенно если приходится работать с несколькими базами одновременно (наложение переменных с одинаковыми именами, проблемы с редактированием и прочее).

detach(dat) # возвращаем обратно

Создание и добавление в базу новых переменных Допустим, мы хотим добавить в базу переменную Campaign, которая будет содержать индекс рекламной кампании, но сохраненный как фактор (factor). Для этого нужно через $ задать имя новой переменной и присвоить ей значение:

dat$Campaign <- factor(dat$Promotion)

Фильтрация наблюдений

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

Можем выбрать несколько переменных (столбцов) и сохранить их в другую базу:

dat[2:4] # 2 и 4 - порядковые номера столбцов, от 2 до 4

Получится маленькая база из трех переменных. Сохраним её как новую базу dat1:

dat1 <- dat[2:4] 

Если выбираем столбцы не подряд, обязательно их номера нужно оформить в виде вектора:

dat[c(1, 3)] # не просто dat[1, 3]

В противном случае получится совсем не то:

dat[1, 3] 
## [1] 1

Это «совсем не то» связано с тем, что, когда мы указываем в квадратных скобках числа через запятую, R воспринимает первое число как номер строки, второе число - как номер столбца (как в матрицах - сначала строка, потом столбец). Можем посмотреть на исходную базу и убедиться в этом. Но зато таким образом мы можем выбирать строки (наблюдения):

dat[, 1:3] # берем все строки, первые 3 столбца

Или столбцы:

dat[1:4, ] # берем первые 4 строки, все столбцы

Фильтрация по условиям

Если хотим отобрать из базы определенные наблюдения, это тоже можно сделать с помощью квадратных скобок. Например, мы хотим выбрать данные за первую неделю:

week1 <- dat[dat$Week == 1, ]

Тут важно не забыть поставить запятую, чтобы R понимал, что мы накладываем условие на строки, а столбцы берём все, что есть. Можем сочетать условия. Например, выбрать данные за первую неделю по компаниям среднего уровня:

View(dat[dat$Week == 1 & dat$MarketSize == "Medium", ])

Для тех же целей можно использовать встроенную функцию subset():

week1 <- subset(dat, Week == 1)
week1_med <- subset(dat, Week == 1 & MarketSize == "Medium")

Конечно, можем отбирать наблюдения и переменные одновременно:

dat_small <- subset(dat, Week == 1 & MarketSize == "Medium", select = c(MarketID, LocationID, AgeOfStore)) 

В коде выше мы выбрали столбцы MarketID, LocationID, AgeOfStore.

Удаление переменных

Чтобы удалить переменные, можно действовать двумя способами:

  • удалить их из базы
  • оставить все остальные переменные в базе

По смыслу это одно и то же. И то, и другое чаще всего осуществляется с помощью функции subset().

Допустим, мы хотим выбрать переменные MarketID и SalesInThousands и сохранить их в новую базу:

# указываем имя базы, оставляем MarketID и SalesInThousands
dat1 <- subset(dat, select = c(MarketID, SalesInThousands)) 

А теперь хотим оставить все, кроме переменных Week и AgeOfStore:

# перед вектором столбцов стоит минус
dat2 <- subset(dat, select = -c(Week, AgeOfStore))