2014-02-27 110 views
2

下面的代码可以做得更像“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”列仅接收一个初始值

不久,输出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) 

您能否请

+1

从正确编码规则:永远不要重新发明轮子。浪费时间,你很可能犯错误,或至少拿出一个非最佳解决方案。 –

+1

我同意不重新发明轮子。我不确定我是否确切知道如何使用车轮。对于上面的例子,你有任何关于正确使用R的建议吗? – CLM

+0

看看str_split和strsplit。一般来说,基R有一些有用的字符串函数,'stringr'包有更多。 –

回答

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") 

另外,concat.split.multiple从我的“splitstackshape”包几乎做它一步到位,但如果你想你的精确的输出,你需要删除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

我已经通过你的代码,我开始明白什么是R(在我短暂的RI经历中一直在写“for”循环,不知何故它不像R;感觉我可以在任何情况下完成其他编程语言,那是当我意识到我做错了什么,因为R应该有许多现成功能)。对于与基准测试的联系 - 我想知道如何测量R中的CPU使用情况。任何有关测量RAM使用情况的软件包的建议? – CLM

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函数。这与使用transformed列表的所有元素调用rbind具有相同的效果。

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 
+0

有趣的方法(+1),但它似乎像这样按行进行,尤其是对所有对'data.frame'的调用,在时间上会非常昂贵。 – A5C1D2H2I1M1N2O1R2T1

+0

当你说时间昂贵,你的意思是CPU时间? – CLM

+0

@AnandaMahto同意,这是一个很慢的交易(4毫秒比我的电脑0.2毫秒)。我认为split/apply/combine是编程R时学到的最重要的范例,所以我认为展示这种方法非常重要。 – josliber