R Заменить записи Dataframe, используя векторизацию - PullRequest
1 голос
/ 22 ноября 2011

Я хотел бы знать, есть ли какой-нибудь способ решить следующую проблему эффективным способом. У меня есть коллекция очков X-Y. Для каждой точки мне нужно сгенерировать определенное количество записей и, наконец, мне нужно собрать воедино все эти записи. Первоначально я делал это с циклом FOR и использовал cbind для хранения data.frame в каждом цикле. Прямо сейчас немного изменил его, определив размеры окончательного стека записей, и я пытаюсь заменить эти 0 на сгенерированные значения. Мой код опубликован ниже (с ** я указываю, где я застрял) ... если вы можете дать мне подсказку на это или даже найти лучшее решение, это было бы прекрасно!

colonies <- read.table(text =             
'  X        Y      Timecount ID_col Age
582906.4 2883317      2004      1  15
583345.9 2883102      2004      2   4
583119.5 2883621      2004      3  13
583385.0 2882933      2004      4   5
583374.0 2882936      2004      5   2
583271.0 2883076      2004      7   5
582898.9 2883229      2004      8   1
582927.9 2883234      2004      9  20
582956.7 2883272      2004     10  13
582958.8 2883249      2004     11   3', header = TRUE)

year = 2004
survival_prob = 0.01
male_prob = 0.5

Present <- colonies$Timecount == year

app <- sum(colonies$Age[Present] >= 4 & colonies$Age[Present] < 10) * 1000 * survival_prob
app2 <- sum(colonies$Age[Present] >= 10 & colonies$Age[Present] < 15) * 10000 * survival_prob
app3 <- sum(colonies$Age[Present] >= 15 & colonies$Age[Present] <= 20) * 100000 * survival_prob

size <- app + app2 + app3

pop <- data.frame(matrix(0,nrow=size,ncol=2))
colnames(pop) <- c("X","Y")

if (dim(pop)[1] > 0){

 #FOR cycle going through each existing point
 for (i in 1:sum(Present)){     

   if (colonies[Present,]$Age[i] < 4) { next
   } else if (colonies[Present,]$Age[i] >= 4 & colonies[Present,]$Age[i] < 10) { alates <- 1000 
   } else if (colonies[Present,]$Age[i] >= 10 & colonies[Present,]$Age[i] < 15) { alates <- 10000 
    } else if (colonies[Present,]$Age[i] >= 15 & colonies[Present,]$Age[i] <= 20) { alates <- 100000 
    }

    indiv <- alates * survival_prob
    #Initialize two coordinate variables based on the established (or existing) colonies
    X_temp <- round(colonies[Present,]$X[i],2)
    Y_temp <- round(colonies[Present,]$Y[i],2)
    distance <- rexp(indiv,rate=1/200)
    theta <- runif(indiv, 0, 2*pi)
    C <- cos(theta)
    S <- sin(theta)
    #XY coords (meters) using polar coordinate transformations
    X <- X_temp + round(S * distance,2)
    Y <- Y_temp + round(C * distance,2)
    pop[,] <- c(X,Y) #******HERE I GOT STUCK...it should be pop[1:indiv,] 
                     #but then it does not work for the next i since it would over write...

    }
    pop$Sex <- rbinom(size,1,male_prob)
    pop$ID <- 1:dim(pop)[1]
}

1 Ответ

1 голос
/ 22 ноября 2011

Я считаю, что это то, что вы искали, хороший выразительный векторизованный R-код.Здесь нет циклов, даже * не применяются команды family или plyr.Вы можете сделать множество вещей, чтобы сделать его более гибким, но векторизация ядра, использующая rep, и одиночные вызовы на случайные расстояния довольно важны.Я понятия не имею, почему был пункт if для измерений поп.Вы должны обращаться с этим по-другому, потому что это не сделано до конца.

year = 2004
survival_prob = 0.01
male_prob = 0.5

# you don't do anything in your for loop or save any of the results if the age is 
# less than 4. I'm going to just remove that from colonies on the assumption that it's 
# larger than posted and comes from a file that you won't change.  Where I edit 
# colonies you might want to work with a copy.
colonies <- colonies[colonies$Age >= 4,]

# only Present selection of colonies is ever used in this code so you could also stop 
# repeatedly selecting... this one I'm imagining you might make a copy of, something 
# like coloniesP in your real code.  In general, you want as little going on in a 
# loop and as little repeating yourself as possible.  Note, this might be memory 
# intensive if colonies is actually very large.  Feel free to going back to selecting 
# since it would happen much less frequently in the new code anyway.
Present <- colonies$Timecount == year
colonies <- colonies[Present,]

# no difference up to size, then it all is
app <- sum(colonies$Age >= 4 & colonies$Age < 10) * 1000 * survival_prob
app2 <- sum(colonies$Age >= 10 & colonies$Age < 15) * 10000 * survival_prob
app3 <- sum(colonies$Age >= 15 & colonies$Age <= 20) * 100000 * survival_prob

size <- app + app2 + app3

#note that ifelse can be used to declare alates as vectors
alates <- ifelse(colonies$Age >= 4 & colonies$Age < 10, 1000, 100000)
alates <- ifelse(colonies$Age >= 10 & colonies$Age < 15, 10000, alates)

# as a consequence, more stuff can be vectorized
indiv <- alates * survival_prob

# we can do some cool stuff with rep to continue vectorizing
# (round when done if you must)
X_temp <- rep(colonies$X, indiv)
Y_temp <- rep(coloines$Y, indiv)

#Initialize two coordinate variables based on the established (or existing) colonies... now as vectors of the entire data frame size
distance <- rexp(size,rate=1/200)
theta <- runif(size, 0, 2*pi)
C <- cos(theta)
S <- sin(theta)
#XY coords (meters) using polar coordinate transformations
X <- X_temp + S * distance
Y <- Y_temp + C * distance
pop <- data.frame(X,Y)  
pop$Sex <- rbinom(size,1,male_prob)
pop$ID <- 1:dim(pop)[1]
# now round... once
pop$X <- round(pop$X,2)
pop$Y <- round(pop$Y,2)

Кроме того, вы можете заметить, что даже если это не может быть векторизация, есть решение вашей проблемы с назначением значенийв поп это очень просто .. не надо.Просто используйте lapply для функции, которая возвращает data.frame, и связывайте список объектов data.frame впоследствии.

...