【问题标题】:R: Turning data in a dataframe to columnsR:将数据框中的数据转换为列
【发布时间】:2026-02-05 09:00:01
【问题描述】:

我有一个这样的数据框

Tag   Date (DD/MM/YYYY)
AA    1/1/2010
AB    2/1/2010
AC    3/1/2010
AA    4/1/2010
AB    5/1/2010
AA    6/1/2010
AB    7/1/2010
AC    8/1/2010

现在,不同标签的数量是有限的(平均少于 10 个)。我需要的是以更舒适的方式处理数据。我已经分析了 Tag 序列数据以找出更频繁的重复模式,在本例中为 (AA,AB,AC)。

现在,我想要将数据转换成这样的形式,以便我可以对其进行操作。

AA        AB        AC
1/1/2010  2/1/2010  3/1/2010
4/1/2010  5/1/2010  NA
6/1/2010  7/1/2010  8/1/2010

我已经看到了这个问题,Turning field values into column names in an R data frame,它与我所需要的非常接近。这样做

>libray(reshape2)
>df<-sqldf("SELECT Tag, Date FROM validData")
>head(dcast(df,Date~Tag))

产量

Using Date as value column: use value_var to override.
Aggregation function missing: defaulting to length

                Date  AF687A AVISOO B32D76 B3DC39 B52C72 DF7EAD DF8E83 DFA521 DFA91A
1 2010-12-23 09:18:50      0      0      0      0      1      0      0      0      0
2 2010-12-23 09:18:52      1      0      0      0      0      0      0      0      0
3 2010-12-23 09:18:54      0      0      0      0      1      0      0      0      0
4 2010-12-23 09:18:57      1      0      0      0      0      0      0      0      0
5 2010-12-23 09:18:58      0      0      0      0      1      0      0      0      0
6 2010-12-23 09:19:00      0      0      0      1      0      0      0      0      0

我想我已经很接近了,但我无法弄清楚最后一步,就像我上面描述的压缩表格一样。有什么线索吗?

【问题讨论】:

  • 您没有明确提及这一点,但您假设彼此跟随的标签之间存在关系(例如:当第 6 次观察没有标签 AC 时,您在想要的结果中使用 NA)。如果您满足于:标签为“AA”的所有日期,然后是标签为“AB”的所有日期(等等),即使它们的长度不同,任务也会简单得多。你能确认一下你想要哪一个吗?
  • 理想情况下,我想要第一个选择,当模式与列表不同时创建一个新行。我知道用命令式语言或类似 PLSQL 的语言比用 R 更容易。但至少我猜第二个选择是一个开始。

标签: r dataframe reshape


【解决方案1】:

我会根据Tag 列中的模式计算您要放入Date 的行和列,然后填充一个新矩阵。

首先为每一行设置你想要匹配的模式;我将使用来自unique 的结果。如果第一个集合缺少一个值(最后一个值除外),这将无法正常工作。

pat <- unique(df$Tag)

然后通过将标签与模式匹配来计算列,并通过注意新模式何时开始来计算行。

col <- match(df$Tag, pat)
row <- cumsum(c(0,diff(col))<=0)

然后创建矩阵并填充它!

out <- matrix(nrow=max(row), ncol=max(col))
colnames(out) <- pat
out[cbind(row, col)] <- df$Date

结果是

> out
     AA         AB         AC        
[1,] "1/1/2010" "2/1/2010" "3/1/2010"
[2,] "4/1/2010" "5/1/2010" NA        
[3,] "6/1/2010" "7/1/2010" "8/1/2010"

【讨论】:

  • 非常好的解决方案,但是,diff(k) 中的k 是什么?,你的意思是col
  • 你解决它的方式确实令人印象深刻,尤其是cumsum(c(0,diff(col))&lt;=0),因此,out[cbind(row, col)] &lt;- df$Date。我希望你能得到更多的积分。
  • 谢谢,亨利克。矩阵索引并不是特别为人所知,但通常可以为此类问题提供直接的答案。
【解决方案2】:

尽管您在问题中描述了一个表格,但在我看来,您确实想列出一个列表。你可以使用 split 函数来做到这一点:

split(df, df$Tag)

$AA
  Tag     Date
1  AA 1/1/2010
4  AA 4/1/2010
6  AA 6/1/2010

$AB
  Tag     Date
2  AB 2/1/2010
5  AB 5/1/2010
7  AB 7/1/2010

$AC
  Tag     Date
3  AC 3/1/2010
8  AC 8/1/2010

要去掉每个列表中的Tag列,可以结合使用lapplysplit

lapply(split(df, df$Tag), function(x)x$Date[drop=TRUE])

$AA
[1] 1/1/2010 4/1/2010 6/1/2010
Levels: 1/1/2010 4/1/2010 6/1/2010

$AB
[1] 2/1/2010 5/1/2010 7/1/2010
Levels: 2/1/2010 5/1/2010 7/1/2010

$AC
[1] 3/1/2010 8/1/2010
Levels: 3/1/2010 8/1/2010

【讨论】:

  • 我了解您的解决方案,但表格会突出显示 NA 值(如果它恰好出现)。目前,这是一个很好的起点,我一定会研究一下。
【解决方案3】:

我的答案使用了很多讨厌的编码(即两个嵌套循环)来获得所需的解决方案,但它确实为您提供了您想要的:

df <- structure(list(Tag = c("AA", "AB", "AC", "AA", "AB", "AA", "AB", 
"AC"), Date = c("1/1/2010", "2/1/2010", "3/1/2010", "4/1/2010", 
"5/1/2010", "6/1/2010", "7/1/2010", "8/1/2010")), .Names = c("Tag", 
"Date"), class = "data.frame", row.names = c(NA, -8L))

l <- nrow(df)
counter <- 1
cols <- c("AA", "AB", "AC")

fin <- data.frame(AA = NULL, AB = NULL, AC = NULL)
tmp <- data.frame(AA = NA, AB = NA, AC = NA)

while(counter < l) {
    tmp <- data.frame(AA = NA, AB = NA, AC = NA)
    for (col in 1:3) {
        if (df[counter,1] == cols[col]) {
            tmp[1,col] <- df[counter,2]
            counter <- counter + 1
        }
    }
    fin <- rbind(fin, tmp)
}

fin

给你:

        AA       AB       AC
1 1/1/2010 2/1/2010 3/1/2010
2 4/1/2010 5/1/2010     <NA>
3 6/1/2010 7/1/2010 8/1/2010

请注意,您可以使用 cols &lt;- unique(sort(df[,1])) 获得更通用的解决方案(for (col in 1:3)fintmp 的创建需要相应地更改)。

此外,这个解决方案根本不是内存效率或任何东西。如果您预先分配等等(在更大的 data.frames 上),您将获得巨大的改进,但对于一种快速而肮脏的方式,它是有效的。

【讨论】:

  • 太棒了!我想这将是做到这一点的方法,只是认为 R 中可能有一个隐藏的包,用一些神秘的参数投射它会按照我的意愿吐出来。不过谢谢! (我会等待接受的解决方案,看看其他人是否提出了一种更 R-ness 的方法来做到这一点)
【解决方案4】:

@Andrie 非常接近解决方案

# here assumed length 3
# but you can calculate it as max
do.call(cbind,lapply(split(mdf$Date,mdf$Tag),"[",seq(3)))


     AA         AB         AC        
[1,] "1/1/2010" "2/1/2010" "3/1/2010"
[2,] "4/1/2010" "5/1/2010" "8/1/2010"
[3,] "6/1/2010" "7/1/2010" NA        

编辑(第一个解决方案没有考虑模式

mdf$grp <- cumsum(1*c(TRUE,diff(as.numeric(factor(mdf$Tag)))<=0))
reshape(mdf,direction="wide",idvar="grp",timevar="Tag")

  grp  Date.AA  Date.AB  Date.AC
1   1 1/1/2010 2/1/2010 3/1/2010
4   2 4/1/2010 5/1/2010     <NA>
6   3 6/1/2010 7/1/2010 8/1/2010

【讨论】: