基于R中另一个df中的一行中的多个值,有条件地填充一个df中的列的更好方法

时间:2014-02-10 21:29:06

标签: r dataframe rows conditional-operator

我想要关联两个数据帧。

第一个数据框(LOC)在5年内研究了大约200只动物,因此大约有100000个点位置。我需要知道每只动物在研究期间的包装情况,但不幸的是,动物经常更换包装。我需要每个点位置都有一个与之关联的包名称,因为我将基于包而不是个人进行进一步的分析。

第二个数据框(PACK)包含这三百只动物的包附属关系,每行代表一个入口日和退出日。我的真实数据是一个包含约700行的表格,每行代表该动物留在包中的时间。例如,在下面的PACK数据框中,动物“W1”在一年的第一天在SunnyLake中“A”,然后在第200天离开并前往RainyLake并在那里呆到年底“B”(闰年)

由于我有这么多动物和多年的转换,我想知道一种方法告诉R哪些动物属于SunnyLake和RainyLake在LOC表中。

到目前为止,我的方法如下,但速度很慢。我对R很新,所以我认为必须有更快,更优雅的方式来做到这一点。如果您看到更好的解决方案,请告诉我(并记住我几个月前刚开始在R工作)!

我的示例数据:

位置表

LOC <- data.frame(matrix(NA, nrow = 8, ncol = 4))
colnames(LOC) <- c("ID", "Yr", "Dy", "Pack")
LOC$ID <- "W1"
LOC$Yr <- rep(c("A", "B"), each = 4)
LOC$Dy <- c(4, 200, 300, 335, 3, 100, 150, 350)
LOC

  ID Yr  Dy Pack
1 W1  A   4   NA
2 W1  A 200   NA
3 W1  A 300   NA
4 W1  A 335   NA
5 W1  B   3   NA
6 W1  B 100   NA
7 W1  B 150   NA
8 W1  B 350   NA

包装表

PACK <- data.frame(matrix(nrow = 3, ncol = 5))
colnames(PACK) <- c("ID", "Pack", "EnterDay", "ExitDay", "Yr")
PACK$ID <- "W1"
PACK$Pack <- c("SunnyLake", "RainyLake", "RainyLake")
PACK$EnterDay <- c(1, 201, 1)
PACK$ExitDay <- c(200, 365, 366)
PACK$Yr <- c("A", "A", "B")
PACK

  ID      Pack EnterDay ExitDay Yr
1 W1 SunnyLake        1     200  A
2 W1 RainyLake      201     365  A
3 W1 RainyLake        1     366  B

我在PACK的每一行中滚动并填充LOC中的“Pack”列的方式

for (i in 1:nrow(PACK)){
  cat("LOC$Pack[LOC$ID == \"", as.character(PACK$ID[i]),"\" & LOC$Yr == \"", as.character(PACK$Yr[i]),"\" & LOC$Dy >= ", PACK$EnterDay[i], " & LOC$Dy <= ", PACK$ExitDay[i],"] <- \"", as.character(PACK$Pack[i]),"\"\n", sep="")  
}

该命令打印以下内容,然后将其粘贴回控制台并运行。

LOC$Pack[LOC$ID == "W1" & LOC$Yr == "A" & LOC$Dy >= 1 & LOC$Dy <= 200] <- "SunnyLake"
LOC$Pack[LOC$ID == "W1" & LOC$Yr == "A" & LOC$Dy >= 201 & LOC$Dy <= 365] <- "RainyLake"
LOC$Pack[LOC$ID == "W1" & LOC$Yr == "B" & LOC$Dy >= 1 & LOC$Dy <= 366] <- "RainyLake"

生成的LOC表如下所示:

LOC
  ID Yr  Dy      Pack
1 W1  A   4 SunnyLake
2 W1  A 200 SunnyLake
3 W1  A 300 RainyLake
4 W1  A 335 RainyLake
5 W1  B   3 RainyLake
6 W1  B 100 RainyLake
7 W1  B 150 RainyLake
8 W1  B 350 RainyLake

2 个答案:

答案 0 :(得分:3)

1)sqldf 试试这个:

library(sqldf)
sqldf("select L.ID, L.Yr, L.Dy, P.Pack from LOC L left join PACK P
       on L.Yr = P.Yr and L.ID = P.ID and L.Dy between P.EnterDay and P.ExitDay")

,并提供:

  ID Yr  Dy      Pack
1 W1  A   4 SunnyLake
2 W1  A 200 SunnyLake
3 W1  A 300 RainyLake
4 W1  A 335 RainyLake
5 W1  B   3 RainyLake
6 W1  B 100 RainyLake
7 W1  B 150 RainyLake
8 W1  B 350 RainyLake

2)dplyr

library(dplyr)

left_join(LOC, PACK, by = c("ID", "Yr")) %.% 
    filter((Dy >= EnterDay & Dy <= ExitDay) | is.na(Pack.y)) %.% 
    select(ID:Dy, Pack.y)

  ID Yr  Dy    Pack.y
1 W1  A   4 SunnyLake
2 W1  A 200 SunnyLake
3 W1  A 300 RainyLake
4 W1  A 335 RainyLake
5 W1  B   3 RainyLake
6 W1  B 100 RainyLake
7 W1  B 150 RainyLake
8 W1  B 350 RainyLake

增加:添加第二个解决方案并改进了两者。修复了dplyr解决方案,以便不删除LOCPACK行。

答案 1 :(得分:2)

首先,不要在开头Pack创建LOC列;没必要。

LOC <- data.frame(matrix(NA, nrow = 8, ncol = 3))
colnames(LOC) <- c("ID", "Yr", "Dy")  # NOTE: No Pack column
LOC$ID <- "W1"
LOC$Yr <- rep(c("A", "B"), each = 4)
LOC$Dy <- c(4, 200, 300, 335, 3, 100, 150, 350)

这是一种使用数据表的方法,对于大型数据集,这种方法可能要快得多。

library(data.table)
LOC      <- data.table(LOC,   key="ID,Yr")
PACK     <- data.table(PACK, key="ID,Yr")
LOC$Pack <-LOC[PACK,all=T][Dy>=EnterDay & Dy<=ExitDay,Pack]
LOC
#   ID Yr  Dy      Pack
# 1 W1  A   4 SunnyLake
# 2 W1  A 200 SunnyLake
# 3 W1  A 300 RainyLake
# 4 W1  A 335 RainyLake
# 5 W1  B   3 RainyLake
# 6 W1  B 100 RainyLake
# 7 W1  B 150 RainyLake
# 8 W1  B 350 RainyLake

这是一个使用数据框的方法(不需要sqldf)。

M <- merge(LOC,PACK,by=c("ID","Yr"))
is.between <- function(x,low,hi)return(x>=low & x<=hi)
LOC$Pack <- with(M,M[is.between(Dy,EnterDay, ExitDay),]$Pack)