А. Б. Шипунов, Е. М. Балдин, П. А. Волкова, А. И. Коробейников, С. А. Назарова
Скачать 3.04 Mb.
|
3.5. Пропущенные данные Не существует ни совершенных наблюдений, ни совершенных экспе- риментов. Чем больше массив данных, тем больше вероятность встре- тить в нем различные недочеты, прежде всего пропущенные данные, которые возникают по тысяче причин — от несовершенства методик, от случайностей во время фиксации данных, от ошибок компьютерных программ и т. д. Строго говоря, пропущенные данные бывают несколь- ких типов. Самый понятный — это «unknown», неизвестное значение, 60 Типы данных когда данные просто не зафиксированы (или потеряны, что, увы, бы- вает нередко). Есть еще «both», когда в процессе наблюдений возник- ло состояние, отвечающее сразу нескольким значениям. Например, ес- ли мы наблюдаем за погодой и отмечаем единицей солнечный день, а нулем — пасмурный, то день с переменной облачностью будет «both» (обычно это значит, что методика наблюдений разработана плохо). На- конец, «not applicable», неприменимое значение, возникает тогда, когда мы обнаружили нечто, логически не совместимое с тем признаком, ко- торый надо фиксировать. Скажем, мы меряем длины клюва у обитате- лей скворечников, и тут нам попалась белка. У нее нет клюва, значит, и длины его тоже нет. Про эти «философские тонкости» хорошо помнить, хотя большинство компьютерных программ, занимающихся анализом данных, не различают этих вариантов. Опыт показывает, что обойтись без пропущенных данных практиче- ски невозможно. Более того, их отсутствие в сколько-нибудь крупном массиве данных может служить основанием для сомнений в их досто- верности. В R пропущенные данные принято обозначать двумя большими бук- вами латинского алфавита «NA». Предположим, что у нас имеется ре- зультат опроса тех же самых семи сотрудников. Их спрашивали, сколь- ко в среднем часов они спят, при этом один из опрашиваемых отвечать отказался, другой ответил «не знаю», а третьего в момент опроса просто не было на рабочем месте. Так возникли пропущенные данные: > h <- c(8, 10, NA, NA, 8, NA, 8) > h [1] 8 10 NA NA 8 NA 8 Как видим, NA надо вводить без кавычек, а R нимало не смущается, что среди цифр находится вроде бы текст. Отметим, что пропущенные данные очень часто столь же разнородны, как и в нашем примере. Од- нако кодируются они одинаково, и об этом не нужно забывать. Теперь о том, как надо работать с полученным вектором h. Если мы просто попробуем посчитать среднее значение (функция mean()), то получим: > mean(h) [1] NA И это «идеологически правильно», поскольку функция может по- разному обрабатывать NA, и по умолчанию она просто сигнализирует о том, что с данными что-то не так. Чтобы высчитать среднее от «непро- пущенной» части вектора, можно поступить одним из двух способов: Выбросы и как их найти 61 > mean(h, na.rm=TRUE) [1] 8.5 > mean(na.omit(h)) [1] 8.5 Первый способ разрешает функции mean() принимать пропущенные данные, а второй делает из вектора h временный вектор без пропущен- ных данных (они просто выкидываются из вектора). Какой из способов лучше, зависит от ситуации. Часто возникает еще одна проблема — как сделать подстановку про- пущенных данных, скажем, заменить все NA на среднюю по выборке. Вот распространенное (но не очень хорошее) решение: > h[is.na(h)] <- mean(h, na.rm=TRUE) > h [1] 8.0 10.0 8.5 8.5 8.0 8.5 8.0 В левой части первого выражения осуществляется индексирование, то есть выбор нужных значений h — таких, которые являются пропу- щенными (is.na()). После того как выражение выполнено, «старые» значения исчезают навсегда, поэтому рекомендуем сначала сохранить старый вектор, скажем, под другим названием: > h.old <- h > h.old [1] 8 10 NA NA 8 NA 8 Есть много других способов замены пропущенных значений, в том числе и очень сложные, основанные на регрессионном, а также дискри- минантном анализе. Некоторые из них реализованы в пакетах mice и cat , существует даже пакет, предоставляющий графический интерфейс для «борьбы» с пропущенными данными, MissingDataGUI. 3.6. Выбросы и как их найти К сожалению, после набора данных возникают не только «пустые ячейки». Очень часто встречаются просто ошибки. Чаще всего это опе- чатки, которые могут возникнуть при ручном наборе. Если данных немного, то можно попытаться выявить такие ошибки вручную. Хуже, если объем данных велик — скажем, более тысячи записей. В этом слу- чае могут помочь методы обработки данных, прежде всего те, которые рассчитаны на выявление выбросов (outliers). Самый простой из них — нахождение минимума и максимума, а для номинальных данных — по- 62 Типы данных строение таблицы частот. К сожалению, такие методы помогают лишь отчасти. Легко найти опечатку в таблице данных роста человека, если кто-то записал 17 см вместо 170 см. Однако ее практически невозможно найти, если вместо 170 см написано 171 см. В этом случае остается на- деяться лишь на статистическую природу данных — чем их больше, тем менее заметны будут ошибки, и на так называемые робастные (устой- чивые к выбросам) методы обработки, о которых мы еще поговорим ниже. 3.7. Меняем данные: основные принципы преобразования Если в исследовании задействовано несколько разных типов дан- ных — параметрические и непараметрические, номинальные и непре- рывные, проценты и подсчеты и т. п., то самым правильным будет при- вести их к какому-то «общему знаменателю». Иногда такое преобразование сделать легко. Даже номинальные дан- ные можно преобразовать в непрерывные, если иметь достаточно ин- формации. Скажем, пол (номинальные данные) можно преобразовать в уровень мужского гормона тестостерона в крови (непрерывные); прав- да, для этого нужна дополнительная информация. Распространенный вариант преобразования — обработка дискретных данных так, как буд- то они непрерывные. В целом это безопасно, но иногда приводит к неприятным последствиям. Совершенно неприемлемый вариант — пре- образование номинальных данных в шкальные. Если данные по своей природе не упорядочены, то их искусственное упорядочение может ра- дикально сказаться на результате. Часто данные преобразуют для того, чтобы они больше походили на параметрические. Если у распределения данных длинные «хвосты», если график распределения (как на рис. 12) лишь отчасти «колоколооб- разный», можно прибегнуть к логарифмированию. Это, наверное, самое частое преобразование. В графических командах R есть даже специаль- ный аргумент ..., log="ось" , где вместо слова ось надо подставить x или y, и тогда соответствую- щая ось графика отобразится в логарифмическом масштабе. Вот самые распространенные методы преобразований с указаниями, как их делать в R (мы предполагаем, что ваши данные находятся в векторе data): • Логарифмическое: log(data + 1). Если распределение скошено вправо, может дать нормальное распределение. Может также де- Меняем данные: основные принципы преобразования 63 лать более линейными зависимости между переменными и урав- нивать дисперсии. «Боится» нулей в данных, поэтому рекоменду- ется прибавлять единицу. • Квадратного корня: sqrt(data). Похоже по действию на логариф- мическое. «Боится» отрицательных значений. • Обратное: 1/(data + 1). Эффективно для стабилизации диспер- сии. «Боится» нулей. • Квадратное: data^2. Если распределение скошено влево, может дать нормальное распределение. Линеаризует зависимости и вы- равнивает дисперсии. • Логит: log(p/(1-p)). Чаще всего применяется к пропорциям. Ли- неаризует так называемую сигмовидную кривую. Кроме логит- преобразования, для пропорций часто используют и арксинус-пре- образование, asin(sqrt(p)) При обработке многомерных данных очень важно, чтобы они были одной размерности. Ни в коем случае нельзя одну колонку в таблице записывать в миллиметрах, а другую — в сантиметрах. В многомерной статистике широко применяется и нормализация данных — приведение разных колонок к общему виду (например, к од- ному среднему значению). Вот как можно, например, нормализовать два разномасштабных вектора: > a <- 1:10 > b <- seq(100, 1000, 100) > d <- data.frame(a, b) > d a b 1 1 100 2 2 200 3 3 300 4 4 400 5 5 500 6 6 600 7 7 700 8 8 800 9 9 900 10 10 1000 > scale(d) a b 64 Типы данных [1,] -1.4863011 -1.4863011 [2,] -1.1560120 -1.1560120 [3,] -0.8257228 -0.8257228 [4,] -0.4954337 -0.4954337 [5,] -0.1651446 -0.1651446 [6,] 0.1651446 0.1651446 [7,] 0.4954337 0.4954337 [8,] 0.8257228 0.8257228 [9,] 1.1560120 1.1560120 [10,] 1.4863011 1.4863011 Как видим, команда scale() приводит векторы «к общему знамена- телю». Обратите внимание на то, что поскольку вектор b был, по сути, просто увеличенным в сто раз вектором a, после преобразования они стали совершенно одинаковыми. 3.8. Матрицы, списки и таблицы данных 3.8.1. Матрицы Матрицы — очень распространенная форма представления данных, организованных в форме таблицы. Про матрицы в R, в общем, нуж- но знать две важные вещи — во-первых, что они могут быть разной размерности и, во-вторых, что матриц как таковых в R, по сути, нет. Начнем с последнего. Матрица в R — это просто специальный тип вектора, обладающий некоторыми добавочными свойствами (атрибу- тами), позволяющими интерпретировать его как совокупность строк и столбцов. Предположим, мы хотим создать простейшую матрицу 2 × 2. Для начала создадим ее из числового вектора: > m <- 1:4 > m [1] 1 2 3 4 > ma <- matrix(m, ncol=2, byrow=TRUE) > ma [,1] [,2] [1,] 1 2 [2,] 3 4 > str(ma) int [1:2, 1:2] 1 3 2 4 > str(m) int [1:4] 1 2 3 4 Матрицы, списки и таблицы данных 65 Как видно, структура (напомним, структуру любого объекта можно посмотреть при помощи очень важной команды str()) объектов m и ma не слишком различается, различается, по сути, лишь их вывод на экран компьютера. Еще очевиднее единство между векторами и матрицами прослеживается, если создать матрицу несколько иным способом: > mb <- m > mb [1] 1 2 3 4 > attr(mb, "dim") <- c(2,2) > mb [,1] [,2] [1,] 1 3 [2,] 2 4 Выглядит как некий фокус. Однако все просто: мы присваиваем вектору mb атрибут dim («dimensions», размерность) и устанавливаем значение этого атрибута в c(2,2), то есть 2 строки и 2 столбца. Чита- телю предоставляется догадаться, почему матрица mb отличается от матрицы ma (ответ см. в конце главы). Мы указали лишь два способа создания матриц, в действительности их гораздо больше. Очень популярно, например, «делать» матрицы из векторов-колонок или строк при помощи команд cbind() или rbind(). Если результат нужно «повернуть» на 90 градусов (транспонировать), используется команда t(). Наиболее распространены матрицы, имеющие два измерения, одна- ко никто не препятствует сделать многомерную матрицу (массив): > m3 <- 1:8 > dim(m3) <- c(2,2,2) > m3 , , 1 [,1] [,2] [1,] 1 3 [2,] 2 4 , , 2 [,1] [,2] [1,] 5 7 [2,] 6 8 66 Типы данных m3 — это трехмерная матрица (или, по-другому, трехмерный мас- сив). Естественно, показать в виде таблицы ее нельзя, поэтому R вы- водит ее на экран в виде серии таблиц. Аналогично можно создать и четырехмерную матрицу (как встроенные данные Titatic). Многомер- ные матрицы в R принято называть «arrays». 3.8.2. Списки Списки — еще один важный тип представления данных. Создавать их, особенно на первых порах, скорее всего, не придется, но знать их осо- бенности необходимо — прежде всего потому, что очень многие функции в R выдают «наружу» именно списки. > l <- list("R", 1:3, TRUE, NA, list("r", 4)) > l [[1]] [1] "R" [[2]] [1] 1 2 3 [[3]] [1] TRUE [[4]] [1] NA [[5]] [[5]][[1]] [1] "r" [[5]][[2]] [1] 4 Видно, что список — это своего рода ассорти. Вектор (и, естественно, матрица) может состоять из элементов одного и того же типа, а вот список — из чего угодно, в том числе (как видно из примера) и из других списков. Теперь поговорим про индексирование, или выбор элементов спис- ка. Элементы вектора выбираются, как мы помним, при помощи функ- ции — квадратной скобки: > h[3] [1] 8.5 Матрицы, списки и таблицы данных 67 Элементы матрицы выбираются так же, только используются несколь- ко аргументов (для двумерных матриц это номер строки и номер столб- ца — именно в такой последовательности): > ma[2, 1] [1] 3 А вот элементы списка выбираются тремя различными методами. Во-первых, можно использовать квадратные скобки: > l[1] [[1]] [1] "R" > str(l[1]) List of 1 $ : chr "R" Здесь очень важно, что полученный объект тоже будет списком. Во-вторых, можно использовать двойные квадратные скобки: > l[[1]] [1] "R" > str(l[[1]]) chr "R" В этом случае полученный объект будет того типа, какого он был бы до объединения в список (поэтому первый объект будет текстовым вектором, а вот пятый — списком). И в-третьих, для индексирования можно использовать имена эле- ментов списка. Но для этого сначала надо их создать: > names(l) <- c("first", "second", "third", "fourth", "fifth") > l$first [1] "R" > str(l$first) chr "R" Для выбора по имени употребляется знак доллара, а полученный объект будет таким же, как при использовании двойной квадратной скобки. На самом деле имена в R могут иметь и элементы вектора, и строки и столбцы матрицы: 68 Типы данных > names(w) <- c("Коля", "Женя", "Петя", "Саша", "Катя", "Вася", + "Жора") > w Коля Женя Петя Саша Катя Вася Жора 69 68 93 87 59 82 72 > rownames(ma) <- c("a1","a2") > colnames(ma) <- c("b1","b2") > ma b1 b2 a1 1 2 a2 3 4 Единственное условие — все имена должны быть разными. Одна- ко знак доллара можно использовать только со списками. Элементы вектора по имени можно отбирать так: > w["Женя"] Женя 68 3.8.3. Таблицы данных И теперь о самом важном типе представления данных — таблицах данных (data frame). Именно таблицы данных больше всего похожи на электронные таблицы Excel и аналогов, и поэтому с ними работают ча- ще всего (особенно начинающие пользователи R). Таблицы данных — это гибридный тип представления, одномерный список из векторов оди- наковой длины . Таким образом, каждая таблица данных — это список колонок, причем внутри одной колонки все данные должны быть одного типа (а вот сами колонки могут быть разного типа). Проиллюстрируем это на примере созданных ранее векторов: > d <- data.frame(weight=w, height=x, size=m.o, sex=sex.f) > d weight height size sex Коля 69 174 L male Женя 68 162 S female Петя 93 188 XL male Саша 87 192 XXL male Катя 59 165 S female Вася 82 168 M male Жора 72 172.5 L male Матрицы, списки и таблицы данных 69 > str(d) ’data.frame’: 7 obs. of 4 variables: $ weight: num 69 68 93 87 59 82 72 $ height: num 174 162 188 192 165 168 172.5 $ size : Ord.factor w/ 5 levels "S"<"M"<"L"<"XL"<..: 3 1 4 5 1 2 3 $ sex : Factor w/ 2 levels "female","male": 2 1 2 2 1 2 2 Поскольку таблица данных является списком, к ней применимы все методы индексации списков. Таблицы данных можно индексировать и как двумерные матрицы. Вот несколько примеров: > d$weight [1] 69 68 93 87 59 82 72 > d[[1]] [1] 69 68 93 87 59 82 72 > d[,1] [1] 69 68 93 87 59 82 72 > d[,"weight"] [1] 69 68 93 87 59 82 72 Очень часто бывает нужно отобрать несколько колонок. Это можно сделать разными способами: > d[,2:4] height size sex Коля 174 L male Женя 162 S female Петя 188 XL male Саша 192 XXL male Катя 165 S female Вася 168 M male Жора 172.5 L male > d[,-1] height size sex Коля 174 L male Женя 162 S female Петя 188 XL male Саша 192 XXL male Катя 165 S female Вася 168 M male Жора 172.5 L male 70 Типы данных К индексации имеет прямое отношение еще один тип данных R — логические векторы . Как, например, отобрать из нашей таблицы только данные, относящиеся к женщинам? Вот один из способов: > d[d$sex=="female",] weight height size sex Женя 68 162 S female Катя 59 165 S female Чтобы отобрать нужные строки, мы поместили перед запятой логи- ческое выражение d$sex==female. Его значением является логический вектор: > d$sex=="female" [1] FALSE TRUE FALSE FALSE TRUE FALSE FALSE Таким образом, после того как «отработала» селекция, в таблице данных остались только те строки, которые соответствуют TRUE, то есть строки 2 и 5. Знак «==», а также знаки «&», «|» и «!» используются для замены соответственно «равен?», «и», «или» и «не». Более сложным случаем отбора является сортировка таблиц дан- ных. Для сортировки одного вектора достаточно применить команду sort() , а вот если нужно, скажем, отсортировать наши данные сначала по полу, а потом по росту, приходится применить операцию посложнее: > d[order(d$sex, d$height), ] weight height size sex Женя 68 162 S female Катя 59 165 S female Вася 82 168 M male Жора 72 172.5 L male Коля 69 174 L male Петя 93 188 XL male Саша 87 192 XXL male Команда order() создает не логический, а числовой вектор, кото- рый соответствует будущему порядку расположения строк. Подумайте, как применить команду order() для того, чтобы отсортировать колон- ки получившейся матрицы по алфавиту (см. ответ в конце главы). * * * Заключая разговор о типах данных, следует отметить, что эти типы вовсе не так резко отграничены друг от друга. Поэтому если вам трудно Матрицы, списки и таблицы данных 71 с первого взгляда сказать, к какому именно типу относятся ваши дан- ные, нужно вспомнить главный вопрос — насколько хорошо данные со- относятся с числовой прямой? Если соотношение хорошее, то данные, скорее всего, интервальные и непрерывные, если плохое — шкальные или даже номинальные. И во-вторых, не следует забывать, что весьма часто удается найти способ преобразования данных в требуемый тип. * * * Ответ к задаче про матрицы |