【问题标题】:How to reformat an R data frame with multiple rows into one row如何将具有多行的 R 数据框重新格式化为一行
【发布时间】:2015-04-28 15:47:11
【问题描述】:

我有如下数据框,我需要将其重新格式化为一行,这样我就可以创建一个新数据框,该数据框是许多更简单的数据框的集合,新数据框中的一行代表所有较简单的原始数据帧之一的数据。

这是原始数据帧格式的一个简单示例:

> myDf = data.frame(Seconds=seq(0,1,.25), s1=seq(0,8,2), s2=seq(1,9,2))
> 
> myDf
  Seconds s1 s2
1    0.00  0  1
2    0.25  2  3
3    0.50  4  5
4    0.75  6  7
5    1.00  8  9

下面是我希望它重新格式化后的样子。每列表示rXsY,其中“rX”表示原始数据帧的行号,“sY”表示原始数据帧的“s1”或“s2”列。新数据框中省略了“秒”列,因为其信息隐含在行号中。

> myNewDf
  r1s1 r1s2 r2s1 r2s2 r3s1 r3s2 r4s1 r4s2 r5s1 r5s2
1    0    1    2    3    4    5    6    7    8    9

我怀疑这真的很简单,可能涉及reshape()melt() 和/或cast() 的某种组合,但正确的咒语让我无法理解。我可以发布我尝试过的内容,但我认为这只会分散可能是一个简单问题的注意力?如果有人希望我这样做,请在 cmets 中询问。

理想的解决方案还会以某种方式基于原始数据框的列名以编程方式生成新的列名,因为列名并不总是相同的。另外,如果不难,我能否以某种方式同时对类似数据框的列表执行相同的操作(所有相同的行数,所有相同的列名,但它们的 s1 和 s2 列中的值不同)?最终我需要一个包含来自多个更简单数据帧的数据的单个数据帧,就像这样......

> myCombinedNewDf # data combined from 4 separate original data frames
  r1s1 r1s2 r2s1 r2s2 r3s1 r3s2 r4s1 r4s2 r5s1 r5s2
1    0    1    2    3    4    5    6    7    8    9
2   10   11   12   13   14   15   16   17   18   19
3   20   21   22   23   24   25   26   27   28   29
4   30   31   32   33   34   35   36   37   38   39

【问题讨论】:

  • 模式是否总是c(df[1, 2:3], df[2, 2:3], df[3, 2:3]) 等?
  • @rawr - 我不确定你的意思;如果您在谈论 s1 & s2 列中的值,则实际数据不遵循任何模式;实际数据是来自传感器读数的“双”(浮点)值。
  • 我觉得你的最后一点应该是一个单独的问题。
  • @phonetagger 我是说你似乎只是按行连接列 s1 和 s2,这不需要任何“重塑”c(t(myDf[, 2:3]))
  • @rawr 抱歉,是的,这始终是我想要的模式。但最终结果需要是一个带有命名列的数据框,适合使用 kernlab 的ksvm 进行训练/预测。

标签: r reshape


【解决方案1】:

使用来自reshape2melt(),您可以这样做:

library(reshape2)

# Melt the data, omitting `Seconds`
df.melted <- melt(myDF[, -1], id.vars = NULL)

# Transpose the values into a single row
myNewDF <- t(df.melted[, 2])

# Assign new variable names
colnames(myNewDF) <- paste0("r", rownames(myDF), df.melted[, 1])

#   r1s1 r2s1 r3s1 r4s1 r5s1 r1s2 r2s2 r3s2 r4s2 r5s2
# 1    0    2    4    6    8    1    3    5    7    9

这会融化数据框,使用第一列(来自原始数据集的变量名)构造新数据集的变量名,并使用第二列(数据值)的转置作为数据行.

如果您想要一种自动组合数据集的方法,您可以更进一步:

# Another data frame
myOtherDF <- data.frame(Seconds = seq(0, 1, 0.25),
                        s1 = seq(1, 9, 2),
                        s2 = seq(0, 8, 2))

# Turn the above steps into a function
colToRow <- function(x) {
    melted <- melt(x[, -1], id.vars = NULL)
    row <- t(melted[, 2])
    colnames(row) <- paste0("r", rownames(x), melted[, 1])
    row
}

# Create a list of the data frames to process
myDFList <- list(myDF, myOtherDF)

# Apply our function to each data frame in the list and append
myNewDF <- data.frame(do.call(rbind, lapply(myDFList, colToRow)))

#   r1s1 r2s1 r3s1 r4s1 r5s1 r1s2 r2s2 r3s2 r4s2 r5s2
# 1    0    2    4    6    8    1    3    5    7    9
# 2    1    3    5    7    9    0    2    4    6    8

【讨论】:

    【解决方案2】:

    可以使用c(t(therelevantdata))逐行提取相关值。

    换句话说:

    Values <- c(t(myDf[-1]))
    

    如果此时名称很重要,您可以这样做:

    Names <- sprintf("r%ss%s", rep(1:5, each = 2), 1:2)
    

    你可以得到一个命名向量:

    setNames(Values, Names)
    # r1s1 r1s2 r2s1 r2s2 r3s1 r3s2 r4s1 r4s2 r5s1 r5s2 
    #    0    1    2    3    4    5    6    7    8    9 
    

    或命名单行 data.frame 与:

    setNames(data.frame(t(Values)), Names)
    #   r1s1 r1s2 r2s1 r2s2 r3s1 r3s2 r4s1 r4s2 r5s1 r5s2
    # 1    0    1    2    3    4    5    6    7    8    9
    

    如果您有data.frames 中的list,正如@cyro111 的回答中所分享的,您可以轻松地执行以下操作:

    do.call(rbind, lapply(myDfList, function(x) c(t(x[-1]))))
    #      [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9] [,10]
    # [1,]    0    1    2    3    4    5    6    7    8     9
    # [2,]   10   11   12   13   14   15   16   17   18    19
    

    使用as.data.frame 转换为data.frame,并使用names &lt;-setNames 添加名称。


    泛化为函数:

    myFun <- function(indf, asVec = TRUE) {
      values <- c(t(indf[-1]))
      Names <- sprintf("r%ss%s", rep(1:nrow(indf), each = ncol(indf[-1])),
                       1:ncol(indf[-1]))
      out <- setNames(values, Names)
      if (isTRUE(asVec)) out
      else (as.data.frame(as.matrix(t(out))))
    }
    

    试试看:

    myFun(myDf)        # Vector
    myFun(myDf, FALSE) # data.frame
    

    listdata.frames 更方便....有很多选择:-)

    dfList1 <- list(
      data.frame(s = 1:2, a1 = 1:2, a2 = 3:4, a3 = 5:6),
      data.frame(s = 1:2, a1 = 11:12, a2 = 31:32, a3 = 51:52)
    )
    
    lapply(dfList1, myFun)
    do.call(rbind, lapply(dfList1, myFun))
    t(sapply(dfList1, myFun))
    as.data.frame(do.call(rbind, lapply(dfList1, myFun)))
    

    【讨论】:

    • OP 认为名称并不总是遵循某种模式,因此 Names &lt;- paste0("r", rep(1:5, each = 2), names(myDf[,2:3])) 涵盖了这种情况。
    • @Frank,如果是这样,那么他们以后打算如何组合不同的数据集?
    • 我认为他说数据值,即 0,1,2,3,... 在他的情况下,不遵循模式。我认为s1s2 的名称始终相同。
    • @cryo111,如果他们不按行列出,他们怎么能不遵循模式?我假设data.frame 的每一行都包含相同数量的元素,除非有NA 或空白。
    • 啊,我读到“因为列名并不总是相同”,这意味着它们在一个用例之间有所不同,而不是在这个假设列表中的 df 到 df。
    【解决方案3】:

    您可以从data.table 的开发版本中尝试dcast,即可以采用多个value.var 列的v1.9.5。创建两列,第一列使用row number ('rn'),第二列使用分组变量('grp'),然后使用dcast。安装详情here

    library(data.table)#v1.9.5+
    dcast(setDT(myDf[-1])[, c('rn1', 'grp') := list(paste0('r', 1:.N), 1)],
                       grp~rn1, value.var=c('s1', 's2'))
    #   grp r1_s1 r2_s1 r3_s1 r4_s1 r5_s1 r1_s2 r2_s2 r3_s2 r4_s2 r5_s2
    #1:   1     0     2     4     6     8     1     3     5     7     9
    

    或者我们可以使用base R中的reshape

     reshape(transform(myDf, rn1=paste0('r', 1:nrow(myDf)), grp=1)[-1], 
             idvar='grp', timevar='rn1', direction='wide')
     #  grp s1.r1 s2.r1 s1.r2 s2.r2 s1.r3 s2.r3 s1.r4 s2.r4 s1.r5 s2.r5
     #1   1     0     1     2     3     4     5     6     7     8     9
    

    更新

    如果我们有多个数据框,我们可以将数据集放在一个列表中,然后使用lapplydcast 或使用rbindlist rbind 列表中的数据集,为每个数据集指定一个分组变量,然后应用@987654336 @ 在整个数据集上。

    使用@Alex A. 帖子中的“myOtherDF”

     myDFList <- list(myDf, myOtherDF)
     dcast(rbindlist(Map(cbind, myDFList, gr=seq_along(myDFList)))[,-1,
           with=FALSE][, rn1:= paste0('r', 1:.N), by=gr],
              gr~rn1, value.var=c('s1', 's2'))
     #   gr r1_s1 r2_s1 r3_s1 r4_s1 r5_s1 r1_s2 r2_s2 r3_s2 r4_s2 r5_s2
     #1:  1     0     2     4     6     8     1     3     5     7     9
     #2:  2     1     3     5     7     9     0     2     4     6     8
    

    【讨论】:

    • 当我执行install.packages("data.table") 然后library(data.table) 时,它告诉我它的版本是data.table 1.9.4,它还说它找不到函数“dcast”(可能是因为它不是版本1.9。 5?)。如何获得 1.9.5 版? ....啊...开发版。
    • @phonetagger 我添加了一个基本 R 选项,以防安装开发版本有困难
    【解决方案4】:

    基础 R 解决方案

    #prepare data
    myDf1 = data.frame(Seconds=seq(0,1,.25), s1=seq(0,8,2), s2=seq(1,9,2))
    myDf2 = data.frame(Seconds=seq(0,1,.25), s1=seq(10,18,2), s2=seq(11,19,2))
    
    myDfList=list(myDf1,myDf2)
    
    #allocate memory
    myCombinedNewDf=data.frame(matrix(NA_integer_,nrow=length(myDfList),ncol=(ncol(myDf1)-1)*nrow(myDf1)))
    
    #reformat
    for (idx in 1:length(myDfList))  myCombinedNewDf[idx,]=c(t(myDfList[[idx]][,-1]))
    
    #set colnames
    colnames(myCombinedNewDf)=paste0("r",sort(rep.int(1:nrow(myDf1),2)),colnames(myDf1)[-1])
    

    根据要求提供处理单独因子列的扩展版本:

    #allocate memory
    #the first column should ultimately be a factor
    #I would use a character column first and later change it to type factor
    #note the stringsAsFactors option!
    myCombinedNewDf=data.frame(rep(NA_character_,length(myDfList)),
                           matrix(NA_integer_,
                                  nrow=length(myDfList),
                                  ncol=(ncol(myDf1)-1)*nrow(myDf1)),
                           stringsAsFactors=FALSE)
    
    #reformat
    for (idx in 1:length(myDfList))  {
      myCombinedNewDf[idx,-1]=c(t(myDfList[[idx]][,-1]))
      #I have just made up some criterion to get one "yes" and one "no"
      #"yes" if the sum of all values is below 100, "no" otherwise
      myCombinedNewDf[idx,1]=if (sum(myDfList[[idx]][,-1])<100) "yes" else "no"
    }
    
    #set colnames
    colnames(myCombinedNewDf)=c("flag",
                            paste0("r",
                                   sort(rep.int(1:nrow(myDf1),2)),
                                   colnames(myDf1)[-1])
                            )
    myCombinedNewDf$flag=factor(myCombinedNewDf$flag)
    myCombinedNewDf
    

    【讨论】:

    • 我认为我最喜欢你的解决方案,因为它似乎最容易理解,但我有一些问题:试图一点一点地走过这个,我试着做@的右手边987654323@ 分配给一个新变量,我当然得到一个数字向量,但如果我这样做newVar = data.frame(c(t(myDfList[[idx]][,-1]))),结果是一个 10 行 1 列的数据框,而不是 1 行 10 列的数据框。有没有办法强制该数据框是水平的而不是垂直的?我问是因为我还需要插入“是”/“否”值。
    • 另外,我认为您的解决方案是唯一预分配内存的解决方案。如果myCombinedNewDf 还有一个额外的“是”/“否”因子列作为第 1 列,我需要做些什么不同的事情来为 myCombinedNewDf 预分配空间? (因子,不是字符串)
    • 根据您的第一个请求:您可以将另一个转置环绕 c(t(myDfList[[idx]][,-1])) 以获得列向量。然后,data.frame(t(c(...))) 给出一个单行 data.frame。
    • 啊,我明白了。问题是c 操作随后将数据强制转换为通用类型。我现在很忙 - 稍后再考虑。
    • 我添加了一些更改以合并附加因子列。这是你想要的吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-14
    • 2012-12-02
    • 1970-01-01
    • 2021-11-09
    相关资源
    最近更新 更多