如何使用“重复”列重新整理数据框?

8

我是一名初学者,想学习使用R进行数据分析。最近我得到了一个预先格式化的环境观测模型数据集,以下是其中的一个示例子集:

date                   site      obs    mod      site           obs    mod
2000-09-01 00:00:00    campus    NA     61.63    city centre    66     56.69
2000-09-01 01:00:00    campus    52     62.55    city centre    NA     54.75
2000-09-01 02:00:00    campus    52     63.52    city centre    56     54.65

基本上,这些数据包括各个站点的污染物每小时观测和模拟浓度的时间序列,在“重复的列”中,即站点-观测-模拟(在示例中我只显示了75个站点中的2个)。我将这个“宽”数据集读入成一个数据框,并希望将其转换为“较窄”的格式,即:
date                   site           obs    mod
2000-09-01 00:00:00    campus         NA     61.63
2000-09-01 01:00:00    campus         52     62.55
2000-09-01 02:00:00    campus         52     63.52
2000-09-01 00:00:00    city centre    66     56.69
2000-09-01 01:00:00    city centre    NA     54.75
2000-09-01 02:00:00    city centre    56     54.65

我认为我应该使用“reshape2”包来完成这个任务。首先,我尝试将数据集melt,然后再进行dcast:

test.melt <- melt(test.data, id.vars = "date", measure.vars = c("site", "obs", "mod"))

然而,它只返回了一半的数据,即第一个站点(“校园”)之后的所有站点(“市中心”)的记录都被截断了:

date                   variable    value
2001-01-01 00:00:00    site        campus
2001-01-01 01:00:00    site        campus
2001-01-01 02:00:00    site        campus
2001-01-01 00:00:00    obs         NA
2001-01-01 01:00:00    obs         52
2001-01-01 02:00:00    obs         52
2001-01-01 00:00:00    mod         61.63
2001-01-01 01:00:00    mod         62.55
2001-01-01 02:00:00    mod         63.52

我随后尝试了 recast:

test.recast <- recast(test.data, date ~ site + obs + mod)

然而,它返回了错误信息:
Error in eval(expr, envir, enclos) : object 'site' not found

我已经尝试搜索过以前的问题,但没有找到类似的情况(如果我错了,请纠正)。请有人帮忙吗?

非常感谢!


1
你实际上想要输出的是什么格式?你给出的第一个样本输出显示为半宽格式。在reshape2语言中,它不完全是“熔化”的。请参见我的更新答案,了解两种选项。 - A5C1D2H2I1M1N2O1R2T1
2个回答

11

在进行一些变量名称清理后,使用基本的 R reshape 可能会更好。

这是您的数据。

test <- read.table(header = TRUE, stringsAsFactors=FALSE,
text = "date             site  obs    mod    site             obs    mod
'2000-09-01 00:00:00'  campus   NA  61.63    'city centre'    66     56.69
'2000-09-01 01:00:00'  campus   52  62.55    'city centre'    NA     54.75
'2000-09-01 02:00:00'  campus   52  63.52    'city centre'    56     54.65")
test
#                  date   site obs   mod      site.1 obs.1 mod.1
# 1 2000-09-01 00:00:00 campus  NA 61.63 city centre    66 56.69
# 2 2000-09-01 01:00:00 campus  52 62.55 city centre    NA 54.75
# 3 2000-09-01 02:00:00 campus  52 63.52 city centre    56 54.65

如果您正确执行了此操作,您应该会得到像我一样的名称:如@chase在他的回答中提到的那样,“重复列名是一种奇怪的现象,不是正常的R行为”–因此我们需要解决这个问题。

注意:这两个选项都会生成一个“时间”变量,您可以放心删除它。您可能想保留它,以防您想要重新排列成宽格式。

  • 选项1:如果您像我一样得到了这样的名称(您应该有的),解决方案很简单。对于第一个站点,只需将“0”附加到站点名称并使用基本的R reshape:

    names(test)[2:4] <- paste(names(test)[2:4], "0", sep=".")
    test <- reshape(test, direction = "long", 
                    idvar = "date", varying = 2:ncol(test))
    rownames(test) <- NULL # reshape makes UGLY rownames
    test
    #                  date time        site obs   mod
    # 1 2000-09-01 00:00:00    0      campus  NA 61.63
    # 2 2000-09-01 01:00:00    0      campus  52 62.55
    # 3 2000-09-01 02:00:00    0      campus  52 63.52
    # 4 2000-09-01 00:00:00    1 city centre  66 56.69
    # 5 2000-09-01 01:00:00    1 city centre  NA 54.75
    # 6 2000-09-01 02:00:00    1 city centre  56 54.65
    
  • 选项2:如果您确实有重复的列名称,修复方式仍然很容易,并且遵循相同的逻辑。首先,创建更好的列名称(使用rep()很容易做到),然后像上面描述的那样使用reshape()

  • names(test)[-1] <- paste(names(test)[-1], 
                             rep(1:((ncol(test)-1)/3), each = 3), sep = ".")
    test <- reshape(test, direction = "long", 
                    idvar = "date", varying = 2:ncol(test))
    rownames(test) <- NULL
    
    ### Or, more convenient:
    # names(test) <- make.unique(names(test))
    # names(test)[2:4] <- paste(names(test)[2:4], "0", sep=".")
    # test <- reshape(test, direction = "long", 
    #                 idvar = "date", varying = 2:ncol(test))
    # rownames(test) <- NULL
    
  • 可选步骤:该表单中的数据仍然不完全是“长格式”。 如果需要,只需要再执行一步:

  • require(reshape2)
    melt(test, id.vars = c("date", "site", "time"))
    #                   date        site time variable value
    # 1  2000-09-01 00:00:00      campus    0      obs    NA
    # 2  2000-09-01 01:00:00      campus    0      obs 52.00
    # 3  2000-09-01 02:00:00      campus    0      obs 52.00
    # 4  2000-09-01 00:00:00 city centre    1      obs 66.00
    # 5  2000-09-01 01:00:00 city centre    1      obs    NA
    # 6  2000-09-01 02:00:00 city centre    1      obs 56.00
    # 7  2000-09-01 00:00:00      campus    0      mod 61.63
    # 8  2000-09-01 01:00:00      campus    0      mod 62.55
    # 9  2000-09-01 02:00:00      campus    0      mod 63.52
    # 10 2000-09-01 00:00:00 city centre    1      mod 56.69
    # 11 2000-09-01 01:00:00 city centre    1      mod 54.75
    # 12 2000-09-01 02:00:00 city centre    1      mod 54.65
    

更新(为了回答评论中的一些问题)

  1. reshape()文档相当令人困惑。最好通过几个例子来理解它的工作原理。具体来说,“时间”不一定是指时间(在您的问题中是“日期”),而是更多地用于面板数据,其中记录在相同ID的不同时间收集。在您的情况下,原始数据中唯一的“id”是“date”列。另一个潜在的“id”是站点,但不是以数据组织的方式。

    试想一下,如果您的数据如下所示:

    test1 <- structure(list(date = structure(1:3, 
        .Label = c("2000-09-01 00:00:00", 
        "2000-09-01 01:00:00", "2000-09-01 02:00:00"), class = "factor"), 
        obs.campus = c(NA, 52L, 52L), mod.campus = c(61.63, 62.55, 
        63.52), obs.cityCentre = c(66L, NA, 56L), mod.cityCentre = c(56.69, 
        54.75, 54.65)), .Names = c("date", "obs.campus", "mod.campus", 
    "obs.cityCentre", "mod.cityCentre"), class = "data.frame", row.names = c(NA, 
    -3L))
    test1
    #                  date obs.campus mod.campus obs.cityCentre mod.cityCentre
    # 1 2000-09-01 00:00:00         NA      61.63             66          56.69
    # 2 2000-09-01 01:00:00         52      62.55             NA          54.75
    # 3 2000-09-01 02:00:00         52      63.52             56          54.65
    

    现在尝试使用reshape(test1, direction = "long", idvar = "date", varying = 2:ncol(test1))。你会发现reshape()将站点名称视为“时间”(可以通过在reshape命令中添加“timevar =“site””来覆盖此行为)。

    direction = “long”时,必须指定哪些列随时间变化。在你的情况下,除了第一列之外的所有列都是如此,因此我使用2:ncol(test)代替“varying”。

  2. test2?那在哪里?

  3. @ Chase's回答下的问题:我认为您误解了melt()的工作原理。基本上,它尝试为您提供数据的最“苗条”的形式。在这种情况下,“最苗条”的形式将是上面描述的“可选步骤”,因为date + site将是构成唯一ID变量所需的最少要求。(我会说“时间”可以安全地删除。)

    一旦您的数据以“可选步骤”中描述的格式存在(我们将假设输出已存储为“test.melt”,您始终可以轻松地围绕不同的方式旋转表格。作为我所说的演示,请尝试以下内容并查看它们的作用。

    dcast(test.melt, date + site ~ variable)
    dcast(test.melt, date ~ variable + site)
    dcast(test.melt, variable + site ~ date)
    dcast(test.melt, variable + date ~ site)
    

    如果你只停留在“选项1”或“选项2”,那么拥有这种灵活性并不容易。


更新(几年后)

现在,“data.table”中的melt可以以类似于reshape的方式“melt”多个列。无论列名是否重复,都应该可以运行。

你可以尝试以下操作:

measure <- c("site", "obs", "mod")
melt(as.data.table(test), measure.vars = patterns(measure), value.name = measure)
#                   date variable        site obs   mod
# 1: 2000-09-01 00:00:00        1      campus  NA 61.63
# 2: 2000-09-01 01:00:00        1      campus  52 62.55
# 3: 2000-09-01 02:00:00        1      campus  52 63.52
# 4: 2000-09-01 00:00:00        2 city centre  66 56.69
# 5: 2000-09-01 01:00:00        2 city centre  NA 54.75
# 6: 2000-09-01 02:00:00        2 city centre  56 54.65

非常感谢您提供的详细解释和解决方案 - 我尝试了 选项1,它确实生成了我想要的输出格式!我可以问两个问题吗? 1. 我参考了 reshape() 的帮助文件,但对参数 idvartimevar 的定义感到困惑。您能解释一下为什么在 选项1 中您指定了它们吗?2. 我认为 test2 中没有名为 "site" 的列,但 reshape() 确实起作用了。为什么? - elarry
非常感谢您花费巨大的精力为我解释事情 - 这比官方的Reshape()文档更好地帮助我理解使用方法。忘记test2吧 - 我想这是我在没有刷新页面的情况下看到的您早期回复之一。 ;) - elarry

7
您有重复的列名是一个有点奇怪的情况,这并不是正常的 R 行为。大多数情况下,R 强制您使用有效的名称,可使用 make.names() 函数。无论如何,我能够复制您的问题。请注意,我创建了自己的示例,因为您的示例无法再现,但逻辑相同。
#Do not force unique names
s <- data.frame(id = 1:3, x = runif(3), x = runif(3), check.names = FALSE)
#-----
  id         x         x
1  1 0.6845270 0.5218344
2  2 0.7662200 0.6179444
3  3 0.4110043 0.1104774

#Now try to melt, note that 1/2 of your x-values are missing!
melt(s, id.vars = 1)
#-----
  id variable     value
1  1        x 0.6845270
2  2        x 0.7662200
3  3        x 0.4110043

解决方案是使你的列名唯一。如我之前所说,R在大多数情况下默认会这样做。然而,你可以通过make.unique()在事后进行操作。

names(s) <- make.unique(names(s))
#-----
[1] "id"  "x"   "x.1"

请注意,x的第二列现在已经追加了一个1。现在melt()函数可以按照您的预期正常工作:
melt(s, id.vars = 1)
#-----
  id variable     value
1  1        x 0.6845270
2  2        x 0.7662200
3  3        x 0.4110043
4  1      x.1 0.5218344
5  2      x.1 0.6179444
6  3      x.1 0.1104774

如果您想将xx.1视为同一变量,可以使用gsub()或其他正则表达式函数来消除不必要的字符。这是我经常使用的工作流程。


这是我的最初想法,但我认为比较并不完全相同。我认为需要的是在修复重复列名后实际上进行直接的reshape()(基本R)。 ([请参见我的答案](https://dev59.com/12jWa4cB1Zd3GeqPve5l#12623269)。) - A5C1D2H2I1M1N2O1R2T1
感谢您指出在这种情况下列名不应相同(或重复)。很抱歉我没有解释清楚 - 原始列是按顺序命名的,但我更改了名称以此形式,认为它可能有助于melt()将同一变量的所有值分组...还有一个问题:在您的示例中,测量变量都是“独立”的(每个变量在1个列中),但在我的示例中,每个记录由3个列(站点-观测-模式)组成。鉴于列名可能不同,是否可能直接从原始数据中将数据重塑为所需的形式?谢谢! - elarry
@elarry,我已经在我的更新答案中尝试回答了你提出的这个问题和其他问题。希望这能有所帮助。 - A5C1D2H2I1M1N2O1R2T1

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