使用数据表对子集执行操作

6

我有一个宽格式的调查数据集。针对一个特定问题,在原始数据中创建了一组变量,以表示在特定月份问卷调查问题的不同事实。

我希望创建一组新的变量,具有与月份无关的名称;这些变量的值将对应于观察到的月份的月份变量问题的值。

请参见以下示例/虚构数据集:

require(data.table)

data <- data.table(month = rep(c('may', 'jun', 'jul'),  each = 5),
                   may.q1 = rep(c('yes', 'no', 'yes'),  each = 5),
                   jun.q1 = rep(c('breakfast', 'lunch', 'dinner'),  each = 5),
                   jul.q1 = rep(c('oranges', 'apples', 'oranges'),  each = 5),
                   may.q2 = rep(c('econ', 'math', 'science'), each = 5),
                   jun.q2 = rep(c('sunny', 'foggy', 'cloudy'), each = 5),
                   jul.q2 = rep(c('no rain', 'light mist', 'heavy rain'), each = 5))

在这份调查中,只有两个问题:“q1”和“q2”。每个问题会在几个月内反复提问。然而,只有当数据中观察到的月份与特定月份的调查问题相匹配时,才包含有效响应。
例如:“5月.q1”对于任何“5月”的观察都被观察为“是”。我想要一个新的“Q1”变量来代表“5月.q1”,“6月.q1”和“7月.q1”。当月份为“5月”时,“Q1”的值将取“5月.q1”的值;当月份为“6月”时,“Q1”的值将取“6月.q1”的值。
如果我想手动使用数据表来实现这一点,我希望得到类似以下的内容:
mdata <- data[month == 'may', c('month', 'may.q1', 'may.q2'), with = F]
setnames(mdata, names(mdata), gsub('may\\.', '', names(mdata)))

我希望将“by = month”重复。

如果我要为数据框使用“plyr”包,我会使用以下方法解决:

require(plyr)
data <- data.frame(data)

mdata <- ddply(data, .(month), function(dfmo) {
    dfmo <- dfmo[, c(1, grep(dfmo$month[1], names(dfmo)))]
    names(dfmo) <- gsub(paste0(dfmo$month[1], '\\.'), '', names(dfmo))
    return(dfmo)
})

任何使用data.table方法的帮助都将不胜感激,因为我的数据很大。谢谢。
3个回答

6

另一种说明方式:

data[, .SD[,paste0(month,c(".q1",".q2")), with=FALSE], by=month]

    month  may.q1     may.q2
 1:   may     yes       econ
 2:   may     yes       econ
 3:   may     yes       econ
 4:   may     yes       econ
 5:   may     yes       econ
 6:   jun   lunch      foggy
 7:   jun   lunch      foggy
 8:   jun   lunch      foggy
 9:   jun   lunch      foggy
10:   jun   lunch      foggy
11:   jul oranges heavy rain
12:   jul oranges heavy rain
13:   jul oranges heavy rain
14:   jul oranges heavy rain
15:   jul oranges heavy rain

但请注意列名来自第一组(之后可以使用setnames重命名)。如果有许多只需要少数列的列,则可能不是最有效的方法。在这种情况下,Arun的解决方案可将其融合为长格式,速度更快。


Matthew Dowle,这绝对比我的melt+cast更快。我在更大的数据上试过了。我的方法根本无法相提并论... 在一个1e5 * 100列的数据上,我的方法需要23秒,而这个方法不到一秒就能完成! - Arun

3

编辑:对于大型数据,这种方法效率很低。可以查看@MatthewDowle的答案,他提供了一种真正快速和简洁的解决方案。

下面是使用data.table的解决方案。

dd <- melt.dt(data, id.var=c("month"))[month == gsub("\\..*$", "", ind)][, 
        ind := gsub("^.*\\.", "", ind)][, split(values, ind), by=list(month)]

melt.dt函数是我编写的一个小函数(仍有改进空间),用于将data.table进行melt操作,类似于plyr中的melt函数(在尝试上面的代码之前,请复制/粘贴下面显示的此函数)。

melt.dt <- function(DT, id.var) {
    stopifnot(inherits(DT, "data.table"))
    measure.var <- setdiff(names(DT), id.var)
    ind <- rep.int(measure.var, rep.int(nrow(DT), length(measure.var)))
    m1  <- lapply(c("list", id.var), as.name)
    m2  <- as.call(lapply(c("factor", "ind"), as.name))
    m3  <- as.call(lapply(c("c", measure.var), as.name))    
    quoted <- as.call(c(m1, ind = m2, values = m3))
    DT[, eval(quoted)]
}

想法:首先使用id.var = month列融化data.table。现在,所有融合的列名都是以month.question的形式出现的。因此,通过从这个融合的列中删除“.question”并与month列相等,我们可以删除所有不必要的条目。一旦我们这样做了,我们就不再需要融合列“ind”中的“month.”。所以,我们使用gsub删除“month.”,只保留q1、q2等。之后,我们需要进行reshape(或cast)。这是通过按month分组并将values列按ind拆分(其中有q1q2)来完成的。这样,您将为每个月份获得2列(然后将它们拼接在一起),以获得所需的输出结果。

1
这个怎么样?
data <- data.table(
                   may.q1 = rep(c('yes', 'no', 'yes'),  each = 5),
                   jun.q1 = rep(c('breakfast', 'lunch', 'dinner'),  each = 5),
                   jul.q1 = rep(c('oranges', 'apples', 'oranges'),  each = 5),
                   may.q2 = rep(c('econ', 'math', 'science'), each = 5),
                   jun.q2 = rep(c('sunny', 'foggy', 'cloudy'), each = 5),
                   jul.q2 = rep(c('no rain', 'light mist', 'heavy rain'), each = 5)
                   )


tmp <- reshape(data, direction = "long", varying = 1:6, sep = ".", timevar = "question")

str(tmp)
## Classes ‘data.table’ and 'data.frame':   30 obs. of  5 variables:
##  $ question: chr  "q1" "q1" "q1" "q1" ...
##  $ may     : chr  "yes" "yes" "yes" "yes" ...
##  $ jun     : chr  "breakfast" "breakfast" "breakfast" "breakfast" ...
##  $ jul     : chr  "oranges" "oranges" "oranges" "oranges" ...
##  $ id      : int  1 2 3 4 5 6 7 8 9 10 ...

如果您想进一步融合此数据,您可以使用melt软件包。
require(reshape2)
## remove the id column if you want (id is the last col so ncol(tmp))
res <- melt(tmp[,-ncol(tmp), with = FALSE], measure.vars = c("may", "jun", "jul"), value.name = "response", variable.name = "month")

str(res)
## 'data.frame':    90 obs. of  3 variables:
##  $ question: chr  "q1" "q1" "q1" "q1" ...
##  $ month   : Factor w/ 3 levels "may","jun","jul": 1 1 1 1 1 1 1 1 1 1 ...
##  $ response: chr  "yes" "yes" "yes" "yes" ...

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接