如何加速r中的循环

时间:2015-02-07 08:37:32

标签: r

我只能想到下面问题陈述的迭代版本。它有效,但速度很慢。这是扁平化数据的一个例子。

对于我的数据框中的每一行,我都计算了这个 - 我有一些值存储在'agevalues'中。每个年龄值都有一个等效列,例如,如果值为50,则等效列名称为age_50。我检查'age1'到'age3'中的任何列是否包含'agevalues'中的值。如果是,则如果存在值50,则将此行的age_250设置为1.

请参阅下面的解决方案

age1=c(20,30,30)
age2=c(10,20,45)
age3=c(50,60,70)
df = data.frame(age1,age2,age3)

#finding unique values of age1...age3 columns
agevalues = NULL
for(i in which(names(df) == "age1"):which(names(df) == "age3"))
{
    agevalues = c(agevalues, unique(df[,i]))
}
uniqueagevalues = unique(agevalues)

#creating a column for each of these age buckets
count = 0;
for(i in 1:length(uniqueagevalues))
{
    newcol = paste("age_",as.character(uniqueagevalues[i]),sep=""); 
    print(newcol)
    df[newcol] = 0
    count = count + 1;
}

#putting 1 if present, else 0
count = 0;
for(i in 1:nrow(df))
{
    for(j in 1:length(uniqueagevalues))
    {
        if(length(which(df[i,which(names(df) == "age1"):which(names(df) == "age3")] == uniqueagevalues[j])))
        {
            coltoaddone = paste("age_",as.character(uniqueagevalues[j]),sep="");
            print(coltoaddone)  
            df[i,coltoaddone] = 1;
        }
        count = count + 1;  
    }
}

输入

> df
  age1 age2 age3
1   20   10   50
2   30   20   60
3   30   45   70

输出

> df
  age1 age2 age3 age_20 age_30 age_10 age_45 age_50 age_60 age_70
1   20   10   50      1      0      1      0      1      0      0
2   30   20   60      1      1      0      0      0      1      0
3   30   45   70      0      1      0      1      0      0      1

3 个答案:

答案 0 :(得分:3)

这是一个替代实现,只使用一个sapply循环和一些前后的矢量化:

# get the unique age values:
agevalues <- unique(unname(unlist(df)))
# check which agevalues are present in which row:
m <- sapply(agevalues, function(x) as.integer(rowSums(df == x) > 0L))
# add the result to the original data and set column names:
df <- setNames(cbind(df, m), c(names(df), paste0("age_", agevalues)))

df
#  age1 age2 age3 age_20 age_30 age_10 age_45 age_50 age_60 age_70
#1   20   10   50      1      0      1      0      1      0      0
#2   30   20   60      1      1      0      0      0      1      0
#3   30   45   70      0      1      0      1      0      0      1

数据:

age1=c(20,30,30)
age2=c(10,20,45)
age3=c(50,60,70)
df = data.frame(age1,age2,age3)

编辑注释:针对每行多个匹配的情况进行调整,仅返回1(不是匹配数)


评论后编辑:

转换为矩阵由sapply完成,因为它使用默认的simplify = TRUE设置。要了解会发生什么,请一步一步地查看:

  • sapply(agevalues, ... )是一个循环,它为每个循环提供一个agevalues元素,即它从第一个元素开始,在这种情况下为20。

接下来会发生什么:

df == 20    #  (because x == 20 in the first loop)
#      age1  age2  age3
#[1,]  TRUE FALSE FALSE      # 1 TRUE in this row
#[2,] FALSE  TRUE FALSE      # 1 TRUE in this row
#[3,] FALSE FALSE FALSE      # 0 TRUE in this row

在此阶段,您已经有一个矩阵,指示条件为TRUE的位置。然后,将其包装在rowSums中,会发生什么:

rowSums(df == 20)
#[1] 1 1 0

它告诉你每行有多少匹配。请注意,如果一行中有2个或更多匹配,rowSums将为该行返回值> 1。因为您只想要返回0或1个条目,所以您可以检查rowSums元素是0(不匹配)还是> 0(任意数量的匹配大于或等于1):

rowSums(df == agevalues[1]) > 0L
#[1]  TRUE  TRUE FALSE

如您所见,这将返回带有TRUE / FALSE条目的逻辑向量。由于您希望在最终输出中使用0/1,因此可以使用以下命令将逻辑转换为整数:

as.integer(rowSums(df == agevalues[1]) > 0L)
# [1] 1 1 0

这些是您在sapply输出中看到的值。而且,由于你是为agevalues中的每个元素做的,sapply能够将列表中的结果简化为这样的矩阵:

sapply(agevalues, function(x) as.integer(rowSums(df == x) > 0L))
#     [,1] [,2] [,3] [,4] [,5] [,6] [,7]
#[1,]    1    0    1    0    1    0    0
#[2,]    1    1    0    0    0    1    0
#[3,]    0    1    0    1    0    0    1

请注意,如果您在simplify = FALSE中指定了sapply,则会获得一个回复列表:

sapply(agevalues, function(x) as.integer(rowSums(df == x) > 0L), simplify = FALSE)
[[1]]
[1] 1 1 0

[[2]]
[1] 0 1 1

[[3]]
[1] 1 0 0

[[4]]
[1] 0 0 1

[[5]]
[1] 1 0 0

[[6]]
[1] 0 1 0

[[7]]
[1] 0 0 1

希望有所帮助。

答案 1 :(得分:3)

您可以尝试 qdapTools

中的mtabulate
library(qdapTools)
df1 <- mtabulate(as.data.frame(t(df)))
names(df1) <- paste('age', names(df1), sep="_")
cbind(df, df1)
#  age1 age2 age3 age_10 age_20 age_30 age_45 age_50 age_60 age_70
#1   20   10   50      1      1      0      0      1      0      0
#2   30   20   60      0      1      1      0      0      1      0
#3   30   45   70      0      0      1      1      0      0      1

数据

df <- structure(list(age1 = c(20L, 30L, 30L), age2 = c(10L, 20L, 45L
), age3 = c(50L, 60L, 70L)), .Names = c("age1", "age2", "age3"
), class = "data.frame", row.names = c("1", "2", "3"))

答案 2 :(得分:2)

尝试:

labels = paste("age",unique(unlist(df)), sep='_')
lst    = lapply(data.frame(t(df)), function(u) as.integer(labels %in% paste("age",u,sep='_')))
setNames(cbind(df,do.call(rbind, lst)),c(names(df),labels))

#   age1 age2 age3 age_20 age_30 age_10 age_45 age_50 age_60 age_70
#X1   20   10   50      1      0      1      0      1      0      0
#X2   30   20   60      1      1      0      0      0      1      0
#X3   30   45   70      0      1      0      1      0      0      1