Подтвердить что ты не робот

Изменить ширину до длинного символа суффиксом вместо числовых суффиксов

Вдохновленный комментарием от @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?
4b9b3361

Ответ 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" из имен столбцов.