Вопрос по r – изменить форму от широкой к длинной с символьными суффиксами вместо числовых суффиксов

15

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

При попытке изменить форму из этого широкого формата в длинный я сталкиваюсь с проблемой. Вот что я делаю, чтобы изменить данные.

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

Обратите внимание на замененные имена столбцов для & quot; имени & quot; и & quot; inc & quot ;; изменения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

Мои вопросы:

Why is R swapping the columns in the example I've provided? Can I get to this result with base R reshape without changing the variable names before reshaping? Are there other approaches that could be considered instead of reshape?
Единственная очевидная причина для тега Stata состоит в том, что пример набора данных находится в собственном формате Stata. Это кажется случайным, поэтому я убрал его. Не стесняйтесь повторно вводить это, если я пропустил что-то важное. Nick Cox

Ваш Ответ

3   ответа
2

merged.stack из моей "расщепленной формы" обрабатывает это с помощьюsep = "var.stubs" построить:

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

Это работает, потому что & quot; .time_1 & quot; создается путем удаления того, что осталось после удаления & quot; var.stubs & quot; из имен столбцов.

8

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

Одна альтернатива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 from & quot; data.table & quot ;, например:

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?

Both packages are much better supported. They update and test their code more extensively than I do. melt is blazing fast. The Hadleyverse approach is actually slower (in many of my tests, even slower than base R's reshape) probably because of having to make the data long, then wide, then performing type conversion. However, some users like its step-by-step approach. The Hadleyverse approach might have some unintended consequences because of the requirement of making the data long before making it wide. That forces all of the measure columns to be coerced to the same type (usually "character") if they are of different types to begin with. Neither have the same convenience of merged.stack. Just look at the code required to get the result ;-)

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?

The main difference is that reshape is not able to handle unbalanced panel datasets. See, for example, "mydf2" as opposed to "mydf" in the tests below. Test cases

Вот некоторые примеры данных. & Quot; mydf & Quot; сбалансирован. & Quot; mydf2 & Quot; не сбалансирован.

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

Замечания:

Base R's reshape would not be able to handle reshaping "mydf2". The "dplyr" + "tidyr" approach would mangle the results in the resulting "varB", "varC", and "varD" because values would be coerced to character. As the benchmarks show, reshape gives reasonable performance.

Note: Because of the difference in time between posting my last answer and the differences in approach, I thought I would share this as a new answer.

Brilliant! Жду с нетерпением.
Красиво и подробно. Не могли бы вы включить новыеmelt функциональность наsplitstackshape для синтаксического сахара это обеспечивает? Или ты уже? Я помню, как обсуждал это некоторое время назад.
@ Арун, в моем списке дел. Увидетьthis gist. A5C1D2H2I1M1N2O1R2T1
11

reshape(dadmom, direction="long",  varying=list(c(2, 4), c(3, 5)), 
        sep="", v.names=c("name", "inc"), timevar="dadmom",
        times=c("d", "m"))

Таким образом, вы на самом деле вложили здесь повторные измерения и имя и inc для мамы и папы. Поскольку у вас есть более одной серии повторных мер, вы должны предоставитьlist чтобы варьировать, что говоритreshape какая группа складывается в другой группе.

Таким образом, два подхода к этой проблеме - предоставить список, как я сделал, или переименовать столбцы так, как зверю R нравятся они, как и вам.

Смотрите мои последние блоги на базеreshape подробнее об этом (особенно вторая ссылка имеет дело с этим):

изменить форму (часть I)

изменить форму (часть II)

@DWin, я думаю, что страница справки также может быть переписана для ясности. Только после прочтения решения Тайлера и повторного посещения страницы я понялThis is canonically a list of vectors of variable names, but it can optionally be a matrix of names, or a single vector of names. Это одна из тех функций, для которых я регулярно открываю страницу помощи, когда использую ее. A5C1D2H2I1M1N2O1R2T1
Не стесняйтесь перемещать это, чтобы вести будущих искателей. +1 за обновление работы.
@TylerRinker, вас могут заинтересовать некоторые другие подходы, которые я опубликовал в качестве альтернативы этому ответу. Конечно, флажок остается вашим, потому что он полностью отвечает на мой вопрос, но я подумал, что требуется обновление, когда появились новые инструменты. A5C1D2H2I1M1N2O1R2T1
@mrdwab, даже после того, как вы укажете объяснение страницы справки по изменению формы, моей маленькой лапше все еще трудно понять ее. Это настолько большая мощная функция, что трудно описать все, что она делает. Я попытался понять, как это работает, на довольно доступном языке. Я надеюсь, что это помогает другим.
Хороший проработанный пример.reshape нужно больше из них.

Похожие вопросы