Изменить ширину до длинного символа суффиксом вместо числовых суффиксов
Вдохновленный комментарием от @gsk3 на вопрос о переформатировании данных, я начал немного экспериментировать с перестройкой данных, где имена переменных имеют символьные суффиксы вместо числовых суффиксов.
В качестве примера я загружу набор данных dadmomw
из одного из веб-страниц UCLA ATS Stata (см. "Пример 4" на веб-странице).
Вот что выглядит с помощью набора данных:
library(foreign)
dadmom <- read.dta("https://stats.idre.ucla.edu/stat/stata/modules/dadmomw.dat")
dadmom
# famid named incd namem incm
# 1 1 Bill 30000 Bess 15000
# 2 2 Art 22000 Amy 18000
# 3 3 Paul 25000 Pat 50000
При попытке переформатировать из этого широкого формата в long, я столкнулся с проблемой. Вот что я делаю, чтобы изменить данные.
reshape(dadmom, direction="long", idvar=1, varying=2:5,
sep="", v.names=c("name", "inc"), timevar="dadmom",
times=c("d", "m"))
# famid dadmom name inc
# 1.d 1 d 30000 Bill
# 2.d 2 d 22000 Art
# 3.d 3 d 25000 Paul
# 1.m 1 m 15000 Bess
# 2.m 2 m 18000 Amy
# 3.m 3 m 50000 Pat
Обратите внимание на имена замененных столбцов для "name" и "inc"; изменение v.names
до c("inc", "name")
не решает проблему.
reshape
кажется очень придирчивым к желанию, чтобы столбцы были названы довольно стандартным способом. Например, я могу правильно изменить данные (и легко), если сначала переименую столбцы:
dadmom2 <- dadmom # Just so we can continue experimenting with the original data
# Change the names of the last four variables to include a "."
names(dadmom2)[2:5] <- gsub("(d$|m$)", "\\.\\1", names(dadmom2)[2:5])
reshape(dadmom2, direction="long", idvar=1, varying=2:5,
timevar="dadmom")
# famid dadmom name inc
# 1.d 1 d Bill 30000
# 2.d 2 d Art 22000
# 3.d 3 d Paul 25000
# 1.m 1 m Bess 15000
# 2.m 2 m Amy 18000
# 3.m 3 m Pat 50000
Мои вопросы:
- Почему R заменяет столбцы в примере, который я предоставил?
- Могу ли я получить этот результат с базой R
reshape
без изменения имен переменных перед изменением формы?
- Существуют ли другие подходы, которые можно было бы рассмотреть вместо
reshape
?
Ответы
Ответ 1
Это работает (чтобы указать, какие столбцы идут с кем):
reshape(dadmom, direction="long", varying=list(c(2, 4), c(3, 5)),
sep="", v.names=c("name", "inc"), timevar="dadmom",
times=c("d", "m"))
Итак, у вас действительно есть вложенные повторные меры; как имя, так и инк для мамы и папы. Поскольку у вас есть более чем одна серия повторяющихся мер, вы должны указать list
на переменную, которая сообщает reshape
, какая группа укладывается в другую группу.
Таким образом, два подхода к этой проблеме состоят в том, чтобы предоставить список, как я, или переименовать столбцы так, как им нравится R-зверь, как вы это делали.
См. мои последние блоги на базе reshape
для получения дополнительной информации об этом (в частности, вторая ссылка касается этого):
изменить (часть I)
изменить (часть II)
Ответ 2
Хотя этот вопрос был конкретно о базе R, полезно знать другие подходы, которые помогут вам достичь такого же результата.
Одной из альтернатив reshape
или merged.stack
будет использование комбинации "dplyr" и "tidry", например:
dadmom %>%
gather(variable, value, -famid) %>% ## Make the entire dataset long
separate(variable, into = c("var", "time"), ## Split "variable" column into two...
sep = "(?<=name|inc)", perl = TRUE) %>% ## ... using regex to split the values
spread(var, value, convert = TRUE) ## Make result wide, converting type
# famid time inc name
# 1 1 d 30000 Bill
# 2 1 m 15000 Bess
# 3 2 d 22000 Art
# 4 2 m 18000 Amy
# 5 3 d 25000 Paul
# 6 3 m 50000 Pat
Другой альтернативой может быть использование melt
из "data.table", например:
library(data.table)
melt(as.data.table(dadmom), ## melt here requres a data.table
measure = patterns("name", "inc"), ## identify columns by patterns
value.name = c("name", "inc"))[ ## specify the resulting variable names
## melt creates a numeric "variable" value. Replace with factored labels
, variable := factor(variable, labels = c("d", "m"))][]
# famid variable name inc
# 1: 1 d Bill 30000
# 2: 2 d Art 22000
# 3: 3 d Paul 25000
# 4: 1 m Bess 15000
# 5: 2 m Amy 18000
# 6: 3 m Pat 50000
Как эти подходы сравниваются с merged.stack
?
- Оба пакета намного лучше поддерживаются. Они обновляют и тестируют свой код более широко, чем я.
-
melt
быстро вспыхивает.
- Метод Hadleyverse на самом деле медленнее (во многих моих тестах, даже медленнее, чем базовый R
reshape
), вероятно, из-за необходимости делать данные длинными, а затем широкими, а затем выполнять преобразование типов. Тем не менее, некоторым пользователям нравится его пошаговый подход.
- Метод Hadleyverse может иметь некоторые непреднамеренные последствия из-за требования сделать данные задолго до его широкого распространения. Это заставляет все столбцы меры быть принуждены к одному типу (обычно "символ" ), если они начинаются с разных типов.
- У них нет одинакового удобства
merged.stack
. Просто посмотрите код, необходимый для получения результата; -)
merged.stack
, вероятно, может извлечь выгоду из упрощенного обновления, что-то вроде эта функция
ReshapeLong_ <- function(indt, stubs, sep = NULL) {
if (!is.data.table(indt)) indt <- as.data.table(indt)
mv <- lapply(stubs, function(y) grep(sprintf("^%s", y), names(indt)))
levs <- unique(gsub(paste(stubs, collapse="|"), "", names(indt)[unlist(mv)]))
if (!is.null(sep)) levs <- gsub(sprintf("^%s", sep), "", levs, fixed = TRUE)
melt(indt, measure = mv, value.name = stubs)[
, variable := factor(variable, labels = levs)][]
}
который затем можно использовать как:
ReshapeLong_(dadmom, stubs = c("name", "inc"))
Как эти подходы сравниваются с базой R reshape
?
- Основное отличие состоит в том, что
reshape
не может обрабатывать несбалансированные наборы данных панели. См., Например, "mydf2" в отличие от "mydf" в приведенных ниже тестах.
Контрольные случаи
Вот некоторые примеры данных. "mydf" сбалансирован. "mydf2" не сбалансирован.
set.seed(1)
x <- 10000
mydf <- mydf2 <- data.frame(
id_1 = 1:x, id_2 = c("A", "B"), varAa = sample(letters, x, TRUE),
varAb = sample(letters, x, TRUE), varAc = sample(letters, x, TRUE),
varBa = sample(10, x, TRUE), varBb = sample(10, x, TRUE),
varBc = sample(10, x, TRUE), varCa = rnorm(x), varCb = rnorm(x),
varCc = rnorm(x), varDa = rnorm(x), varDb = rnorm(x), varDc = rnorm(x))
mydf2 <- mydf2[-c(9, 14)] ## Make data unbalanced
Вот некоторые функции для проверки:
f1 <- function(mydf) {
mydf %>%
gather(variable, value, starts_with("var")) %>%
separate(variable, into = c("var", "time"),
sep = "(?<=varA|varB|varC|varD)", perl = TRUE) %>%
spread(var, value, convert = TRUE)
}
f2 <- function(mydf) {
melt(as.data.table(mydf),
measure = patterns(paste0("var", c("A", "B", "C", "D"))),
value.name = paste0("var", c("A", "B", "C", "D")))[
, variable := factor(variable, labels = c("a", "b", "c"))][]
}
f3 <- function(mydf) {
merged.stack(mydf, var.stubs = paste0("var", c("A", "B", "C", "D")), sep = "var.stubs")
}
## Won't run with "mydf2". Should run with "mydf"
f4 <- function(mydf) {
reshape(mydf, direction = "long",
varying = lapply(c("varA", "varB", "varC", "varD"),
function(x) grep(x, names(mydf))),
sep = "", v.names = paste0("var", c("A", "B", "C", "D")),
timevar="time", times = c("a", "b", "c"))
}
Производительность теста:
library(microbenchmark)
microbenchmark(f1(mydf), f2(mydf), f3(mydf), f4(mydf))
# Unit: milliseconds
# expr min lq mean median uq max neval
# f1(mydf) 463.006547 492.073086 528.533319 514.189548 538.910756 867.93356 100
# f2(mydf) 3.737321 4.108376 6.674066 4.332391 4.761681 47.71142 100
# f3(mydf) 60.211254 64.766770 86.812077 87.040087 92.841747 262.89409 100
# f4(mydf) 40.596455 43.753431 61.006337 48.963145 69.983623 230.48449 100
замечания:
- База R
reshape
не сможет обрабатывать переформатирование "mydf2" .
- Подход "dplyr" + "tidyr" приведет к искажению результатов в результате "varB", "varC" и "varD", поскольку значения будут принудительно привязаны к символу.
- Как показывают тесты,
reshape
дает разумную производительность.
Примечание. Из-за разницы во времени между отправкой моего последнего ответа и различиями в подходе я решил поделиться этим как новый ответ.
Ответ 3
merged.stack
из моего "splitstackshape" обрабатывает это, используя конструкцию sep = "var.stubs"
:
library(splitstackshape)
merged.stack(dadmom, var.stubs = c("inc", "name"), sep = "var.stubs")
# famid .time_1 inc name
# 1: 1 d 30000 Bill
# 2: 1 m 15000 Bess
# 3: 2 d 22000 Art
# 4: 2 m 18000 Amy
# 5: 3 d 25000 Paul
# 6: 3 m 50000 Pat
Обратите внимание, что, поскольку в переменных, которые сложены, нет реального разделителя, мы можем просто вычеркнуть var.stubs
из имен, чтобы создать переменные времени. Использование sep = "var.stubs"
эквивалентно выполнению sep = "inc|name"
.
Это работает, потому что ".time_1" создается путем удаления того, что осталось после удаления "var.stubs" из имен столбцов.