如何用“重复出现”的列重塑数据框?
Posted
技术标签:
【中文标题】如何用“重复出现”的列重塑数据框?【英文标题】:How to reshape a dataframe with "reoccurring" columns? 【发布时间】:2012-09-19 05:08:44 【问题描述】:我是使用 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
基本上,数据包括“重复列”中各个站点的每小时观测和模拟污染物浓度的时间序列,即站点 - obs - mod(在示例中,我只显示了总共 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”来做到这一点。首先我尝试融化然后 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
然后我尝试重铸:
test.recast <- recast(test.data, date ~ site + obs + mod)
但是,它返回错误消息:
Error in eval(expr, envir, enclos) : object 'site' not found
我试图搜索以前的问题,但没有找到类似的场景(如果我错了,请纠正我)。有人可以帮我解决这个问题吗?
非常感谢!
【问题讨论】:
您实际上希望输出的格式是什么?您提供的第一个示例输出显示了半宽格式。在reshape2
语言中,它不是完全“融化”的。请参阅我对这两个选项的更新答案。
【参考方案1】:
在做了一些变量名清理之后,你最好使用 base 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 提到的in his answer,“重复的列名有点奇怪,不是正常的 R 行为”- - 所以我们必须解决这个问题。
注意:这两个选项都会生成一个“时间”变量,您可以继续删除它。您可能想保留它以防万一您想重新调整为宽格式。
选项 1:如果你有像我一样的名字(你应该有),解决方案很简单。对于第一个站点,只需将“0”附加到站点名称并使用base 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
更新(尝试解决 cmets 提出的一些问题)
reshape()
文档非常混乱。最好通过几个示例来了解它的工作原理。具体来说,“时间”不必指代时间(问题中的“日期”),而更多的是指面板数据,其中记录是在不同时间为同一 ID 收集的。在您的情况下,原始数据中唯一的“id”是“日期”列。另一个潜在的“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
”。
test2
?那是哪里?
@Chase 回答下的问题:我认为您误解了 melt()
应该如何工作。基本上,它试图让您获得“最精简”的数据形式。在这种情况下,最精简的形式将是上述“可选步骤”,因为date
+ site
将是组成唯一 ID 变量所需的最小值。 (我会说“time
”可以安全地删除。)
一旦您的数据采用“可选步骤”中描述的格式(我们假设输出已存储为“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
类似的方式“融化”多个列。无论列名是否重复,它都应该有效。
您可以尝试以下方法:
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
【讨论】:
非常感谢您的详细解释和解决方案 - 我尝试了Option 1
,它确实产生了我想要的输出格式!我可以再问 2 个问题吗? 1.我参考了reshape()
的帮助文件,但是被idvar
和timevar
的参数定义弄糊涂了。您能解释一下为什么在Option 1
中您按原样指定它们吗? 2. 我以为test2
中没有名为"site"
的列,但reshape()
确实有效。为什么?
非常感谢你付出了巨大的努力为我解释得这么好——这比官方的 Reshape()
文档在帮助我理解使用方面要好得多。忘记test2
- 我认为这是我在没有刷新页面的情况下看到的您之前的回复之一。 ;)【参考方案2】:
您有重复的列名这一事实有点奇怪,并且不是正常的 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
此时,如果您想将x
和x.1
视为同一个变量,我想一点gsub()
或其他正则表达式函数来摆脱违规字符。这是我经常使用的工作流程。
【讨论】:
这是我最初的想法,但我认为比较不太一样。我认为在修复重复的列名之后,这里需要的实际上是一个简单的reshape()
(base R)。 (See my answer.)
感谢您指出在这种情况下列名不应相同(或重复出现)。抱歉,我没有解释清楚 - 原始列是按顺序命名的,但我将名称更改为这种形式,认为这可能有助于 melt()
对同一变量的所有值进行分组......还有一个问题:在你的例子中测量变量都是“独立的”(每个在 1 列中),但在我的每条记录中包含 3 列(site-obs-mod)。鉴于列名可能不同,是否可以将数据直接从原始数据重塑为我想要的形式?谢谢!
@elarry,我已经尝试解决这个问题以及您在我更新的答案中提出的其他问题。我希望这会有所帮助。以上是关于如何用“重复出现”的列重塑数据框?的主要内容,如果未能解决你的问题,请参考以下文章