如何压缩此代码?

时间:2014-02-27 15:33:50

标签: r

以下代码可以更“R like”吗?

给出data.frame inDF:

    V1         V2       V3        V4  
1   a          ha       1;2;3     A
2   c          hb       4         B
3   d          hc       5;6       C
4   f          hd       7         D

内部df我想

  1. 查找“V3”列中包含多个值的所有行 以“;”分隔。
  2. 然后将各行复制的次数等于“V3”列中单个值的数量,
  3. 然后每个复制行在“V3”列中只接收一个初始值
  4. 很快,输出data.frame(= outDF)将如下所示:

        V1         V2       V3        V4  
    1   a          ha       1         A
    1   a          ha       2         A
    1   a          ha       3         A
    2   c          hb       4         B
    3   d          hc       5         C
    3   d          hc       6         C
    4   f          hd       7         D
    

    所以,如果从inDF我想到outDF,我会写下面的代码:

    #load inDF from csv file
    inDF <- read.csv(file='example.csv', header=FALSE, sep=",", fill=TRUE)  
    
    #search in inDF, on the V3 column, all the cells with multiple values
    rowlist <- grep(";", inDF[,3])
    
    # create empty data.frame and add headers from "headDF"
    xDF <- data.frame(matrix(0, nrow=0, ncol=4))
    colnames(xDF)=colnames(inDF)
    
    #take every row from the inDF data.frame which has multiple values in col3 and break it in several rows with only one value
    
    for(i in rowlist[])
    { 
      #count the number of individual values in one cell
      value_nr <- str_count(inDF[i,3], ";"); value_nr <- value_nr+1
    
      # replicate each row a number of times equal with its value number, and transform it to character
      extracted_inDF <- inDF[rep(i, times=value_nr[]),]
      extracted_inDF <- data.frame(lapply(extracted_inDF, as.character), stringsAsFactors=FALSE)
    
      # split the values in V3 cell in individual values, place them in a list
      value_ls <- str_split(inDF[i, 3], ";")
    
      #initialize f, to use it later to increment both row number and element in the list of values
      f = 1
    
      # replace the multiple values with individual values
      for(j in extracted_inDF[,3])
    
        {
        extracted_inDF[f,3] <- value_ls[[1]][as.integer(f)]
        f <- f+1
      }
    
      #put all the "demultiplied" rows in xDF
      xDF <- merge(extracted_inDF[], xDF[], all=TRUE)
    }
    
    # delete the rows with multiple values from the inDF
    inDF <- inDF[-rowlist[],]
    
    #create outDF
    outDF <- merge(inDF, xDF, all=TRUE)
    

    你能不能

2 个答案:

答案 0 :(得分:3)

我不确定我是否会谈论你是否以“正确”或“错误”方式使用R ...我主要只是用它来回答有关Stack Overflow的问题。 : - )

但是,有许多方法可以改进您的代码。对于初学者,是的,您应该尝试熟悉预定义的功能。它们通常效率更高,并且可以使您的代码对同一语言的其他用户更加透明。尽管你简明扼要地描述了你想要实现的目标,并且我几乎立即知道了答案,但我发现你的代码令人生畏。

我会将您的问题分解为两个主要部分:(1)拆分数据和(2)将其与原始数据集重新组合。

对于第1部分 :您显然知道您需要的一些功能 - 或者至少是您需要的主要功能:strsplit。如果您使用strsplit,则会看到它返回list,但您需要一个简单的vector。你怎么到那的?寻找unlist。问题的第一部分现在已经解决了。

对于第2部分 :首先需要确定复制原始数据集的每一行所需的次数。为此,您可以浏览list(例如,使用l/s/v-apply)并计算每个项目的length。我选择了sapply因为我知道它会创建一个我可以用rep的向量。

然后,如果您已经使用了data.frame,特别是在提取数据时,您会发现mydf[c(1, 1, 1, 2), ]会产生data.frame,其中第一行是再重复两次。知道了这一点,我们就可以使用我们刚刚进行的length计算来“扩展”我们原来的data.frame

最后,使用展开的data.frame,我们只需要使用未列出的值替换相关列。


以上是上述行动。我已将您的数据集命名为“mydf”:

V3 <- strsplit(mydf$V3, ";", fixed=TRUE)
sapply(V3, length)    ## How many times to repeat each row?
# [1] 3 1 2 1
## ^^ Use that along with `[` to "expand" your data.frame
mydf2 <- mydf[rep(seq_along(V3), sapply(V3, length)), ]
mydf2$V3 <- unlist(V3)
mydf2
#     V1 V2 V3 V4
# 1    a ha  1  A
# 1.1  a ha  2  A
# 1.2  a ha  3  A
# 2    c hb  4  B
# 3    d hc  5  C
# 3.1  d hc  6  C
# 4    f hd  7  D

分享更多选择......

“data.table”包对于类似的东西实际上非常有用。

library(data.table)
DT <- data.table(mydf)
DT2 <- DT[, list(new = unlist(strsplit(as.character(V3), ";", fixed = TRUE))), by = V1]
merge(DT, DT2, by = "V1")

或者,我的“splitstackshape”包中的concat.split.multiple几乎可以一步完成,但是如果你想要你的确切输出,你需要删除NA值并重新排序行。

library(splitstackshape)
df2 <- concat.split.multiple(mydf, split.cols="V3", seps=";", direction="long")
df2 <- df2[complete.cases(df2), ]   ## Optional, perhaps
df2[order(df2$V1), ]                ## Optional, perhaps

答案 1 :(得分:2)

在这种情况下,您可以使用split-apply-combine范例来重塑数据。

您希望按行分割inDF,因为您希望分别对每一行进行操作。我在这里使用了split函数将其拆分为行:

spl = split(inDF, 1:nrow(inDF))

spl是一个列表,其中包含inDF中每行的1行数据框。

接下来,您需要应用一个函数将拆分数据转换为您需要的最终格式。在这里,我将使用lapply函数转换1行数据帧,使用strsplit将变量V3拆分为适当的部分:

transformed = lapply(spl, function(x) {
  data.frame(V1=x$V1, V2=x$V2, V3=strsplit(x$V3, ";")[[1]], V4=x$V4)
})

tranformed现在是一个列表,其中第一个元素具有3行数据帧,第三个元素具有2行数据帧,第二个元素具有1行数据帧。

最后一步是将此列表合并到outDF,使用do.callrbind功能。这与使用rbind列表的所有元素调用transformed具有相同的效果。

outDF = do.call(rbind, transformed)

这产生了所需的最终数据框:

outDF
#     V1 V2 V3 V4
# 1.1  a ha  1  A
# 1.2  a ha  2  A
# 1.3  a ha  3  A
# 2    c hb  4  B
# 3.1  d hc  5  C
# 3.2  d hc  6  C
# 4    f hd  7  D