以下代码可以更“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我想
很快,输出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)
你能不能
答案 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.call
和rbind
功能。这与使用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