比R中的嵌套for循环更有效的方法--匹配
我试图匹配名字、姓氏和名字相同的人,并保留ID的最小数值 我在下面创建了一个测试数据库(比我的实际数据集小得多),并编写了一个嵌套的for循环,看起来它正在做它应该做的事情 但在更大的数据集上,速度非常慢 我对应用函数比较陌生,但它们在应用函数方面似乎比数据争用更直观 对于我在这里做的事情,有什么更有效的替代方案?我相信有一个简单的解决办法,如果我在这里问这个问题,我会摇头,但我不会去问比R中的嵌套for循环更有效的方法--匹配,r,for-loop,apply,R,For Loop,Apply,我试图匹配名字、姓氏和名字相同的人,并保留ID的最小数值 我在下面创建了一个测试数据库(比我的实际数据集小得多),并编写了一个嵌套的for循环,看起来它正在做它应该做的事情 但在更大的数据集上,速度非常慢 我对应用函数比较陌生,但它们在应用函数方面似乎比数据争用更直观 对于我在这里做的事情,有什么更有效的替代方案?我相信有一个简单的解决办法,如果我在这里问这个问题,我会摇头,但我不会去问 dta.test<- NULL dta.test$Person_id <- c(1,2,3,4,
dta.test<- NULL
dta.test$Person_id <- c(1,2,3,4,5,6,7,8,9,10, 11)
dta.test$FirstName <- c("John", "James", "John", "Alex", "Alexander", "Jonathan", "John", "Alex", "James", "John", "John")
dta.test$LastName <- c("Smith", "Jones", "Jones", "Jones", "Jones", "Smith", "Jones", "Smith", "Johnson", "Smith", "Smith")
dta.test$DOB <- c("2001-01-01", "2002-01-01", "2003-01-01", "2004-01-01", "2004-01-01", "2001-01-01", "2003-01-01", "2006-01-01", "2006-01-01", "2001-01-01", "2009-01-01")
dta.test$Actual_ID <- c(1, 2, 3, 4, 5, 6, 3, 8, 9, 1, 11)
dta.test <- as.data.frame(dta.test)
for(i in unique(dta.test$FirstName))
for(j in unique(dta.test$LastName))
for (k in unique (dta.test$DOB))
{
{
{
dta.test$Person_id[dta.test$FirstName==i & dta.test$LastName==j & dta.test$DOB==k] <- min(dta.test$Person_id[dta.test$FirstName==i & dta.test$LastName==j & dta.test$DOB==k], na.rm=T)
}
}
}
dta.test这里有一个dplyr
解决方案
library(dplyr)
dta.test %>%
group_by(FirstName, LastName, DOB) %>%
mutate(Person_id = min(Person_id))
# A tibble: 11 x 5
# Groups: FirstName, LastName, DOB [9]
# Person_id FirstName LastName DOB Actual_ID
# <dbl> <fct> <fct> <fct> <dbl>
# 1 1. John Smith 2001-01-01 1.
# 2 2. James Jones 2002-01-01 2.
# 3 3. John Jones 2003-01-01 3.
# 4 4. Alex Jones 2004-01-01 4.
# 5 5. Alexander Jones 2004-01-01 5.
# 6 6. Jonathan Smith 2001-01-01 6.
# 7 3. John Jones 2003-01-01 3.
# 8 8. Alex Smith 2006-01-01 8.
# 9 9. James Johnson 2006-01-01 9.
# 10 1. John Smith 2001-01-01 1.
# 11 11. John Smith 2009-01-01 11.
库(dplyr)
dta.test%>%
分组人(名字、姓氏、出生日期)%>%
变异(Person_id=min(Person_id))
#A tibble:11 x 5
#组:名字、姓氏、出生日期[9]
#人员id名字姓氏DOB实际id
#
# 1 1. 约翰·史密斯2001-01-01 1。
# 2 2. 詹姆斯·琼斯2002-01-01 2。
# 3 3. 约翰·琼斯2003-01-01 3。
# 4 4. 亚历克斯·琼斯2004-01-01 4。
# 5 5. 亚历山大·琼斯2004-01-01 5。
# 6 6. 乔纳森·史密斯2001-01-01 6。
# 7 3. 约翰·琼斯2003-01-01 3。
# 8 8. 亚历克斯·史密斯2006-01-01 8。
# 9 9. 詹姆斯·约翰逊2006-01-01 9。
# 10 1. 约翰·史密斯2001-01-01 1。
# 11 11. 约翰·史密斯2009-01-01 11。
编辑-添加性能比较
for_loop_approach <- function() {
for(i in unique(dta.test$FirstName))
for(j in unique(dta.test$LastName))
for (k in unique (dta.test$DOB))
{
{
{
dta.test$Person_id[dta.test$FirstName==i & dta.test$LastName==j & dta.test$DOB==k] <- min(dta.test$Person_id[dta.test$FirstName==i & dta.test$LastName==j & dta.test$DOB==k], na.rm=T)
}
}
}
}
dplyr_approach <- function() {
require(dplyr)
dta.test %>%
group_by(FirstName, LastName, DOB) %>%
mutate(Person_id = min(Person_id))
}
library(microbenchmark)
microbenchmark(for_loop_approach(), dplyr_approach(), unit="relative", times=100L)
Unit: relative
expr min lq mean median uq max neval
for_loop_approach() 20.97948 20.6478 18.8189 17.81437 17.91815 11.76743 100
dplyr_approach() 1.00000 1.0000 1.0000 1.00000 1.00000 1.00000 100
There were 50 or more warnings (use warnings() to see the first 50)
for_loop_方法这里有一个dplyr
解决方案
library(dplyr)
dta.test %>%
group_by(FirstName, LastName, DOB) %>%
mutate(Person_id = min(Person_id))
# A tibble: 11 x 5
# Groups: FirstName, LastName, DOB [9]
# Person_id FirstName LastName DOB Actual_ID
# <dbl> <fct> <fct> <fct> <dbl>
# 1 1. John Smith 2001-01-01 1.
# 2 2. James Jones 2002-01-01 2.
# 3 3. John Jones 2003-01-01 3.
# 4 4. Alex Jones 2004-01-01 4.
# 5 5. Alexander Jones 2004-01-01 5.
# 6 6. Jonathan Smith 2001-01-01 6.
# 7 3. John Jones 2003-01-01 3.
# 8 8. Alex Smith 2006-01-01 8.
# 9 9. James Johnson 2006-01-01 9.
# 10 1. John Smith 2001-01-01 1.
# 11 11. John Smith 2009-01-01 11.
库(dplyr)
dta.test%>%
分组人(名字、姓氏、出生日期)%>%
变异(Person_id=min(Person_id))
#A tibble:11 x 5
#组:名字、姓氏、出生日期[9]
#人员id名字姓氏DOB实际id
#
# 1 1. 约翰·史密斯2001-01-01 1。
# 2 2. 詹姆斯·琼斯2002-01-01 2。
# 3 3. 约翰·琼斯2003-01-01 3。
# 4 4. 亚历克斯·琼斯2004-01-01 4。
# 5 5. 亚历山大·琼斯2004-01-01 5。
# 6 6. 乔纳森·史密斯2001-01-01 6。
# 7 3. 约翰·琼斯2003-01-01 3。
# 8 8. 亚历克斯·史密斯2006-01-01 8。
# 9 9. 詹姆斯·约翰逊2006-01-01 9。
# 10 1. 约翰·史密斯2001-01-01 1。
# 11 11. 约翰·史密斯2009-01-01 11。
编辑-添加性能比较
for_loop_approach <- function() {
for(i in unique(dta.test$FirstName))
for(j in unique(dta.test$LastName))
for (k in unique (dta.test$DOB))
{
{
{
dta.test$Person_id[dta.test$FirstName==i & dta.test$LastName==j & dta.test$DOB==k] <- min(dta.test$Person_id[dta.test$FirstName==i & dta.test$LastName==j & dta.test$DOB==k], na.rm=T)
}
}
}
}
dplyr_approach <- function() {
require(dplyr)
dta.test %>%
group_by(FirstName, LastName, DOB) %>%
mutate(Person_id = min(Person_id))
}
library(microbenchmark)
microbenchmark(for_loop_approach(), dplyr_approach(), unit="relative", times=100L)
Unit: relative
expr min lq mean median uq max neval
for_loop_approach() 20.97948 20.6478 18.8189 17.81437 17.91815 11.76743 100
dplyr_approach() 1.00000 1.0000 1.0000 1.00000 1.00000 1.00000 100
There were 50 or more warnings (use warnings() to see the first 50)
for_loop_方法我实施了一种基本R方法,而不是dplyr方法,结果(根据microbenchmark)比CPak的dplyr方法快7.46倍,比for loop方法快139.4倍。我刚刚使用了match
和paste0
函数来实现此功能,它将自动保留最小的匹配id:
dta.test[, "Actual_id"] <- match(paste0(dta.test$FirstName, dta.test$LastName, dta.test$DOB), paste0(dta.test$FirstName, dta.test$LastName, dta.test$DOB))
在您的真实数据中,我希望person id不是那么简单(不仅仅是一个整数),并且不按数字顺序运行,例如
dta.test$Person_id <- paste0(LETTERS[1:11],1:11)
给予:
Person_id FirstName LastName DOB Actual_id
1 A1 John Smith 2001-01-01 A1
2 B2 James Jones 2002-01-01 B2
3 C3 John Jones 2003-01-01 C3
4 D4 Alex Jones 2004-01-01 D4
5 E5 Alexander Jones 2004-01-01 E5
6 F6 Jonathan Smith 2001-01-01 F6
7 G7 John Jones 2003-01-01 C3
8 H8 Alex Smith 2006-01-01 H8
9 I9 James Johnson 2006-01-01 I9
10 J10 John Smith 2001-01-01 A1
11 K11 John Smith 2009-01-01 K11
我已经实现了一种基本R方法,而不是dplyr方法,它比CPak的dplyr方法快7.46倍,比for-loop方法快139.4倍。我刚刚使用了match
和paste0
函数来实现此功能,它将自动保留最小的匹配id:
dta.test[, "Actual_id"] <- match(paste0(dta.test$FirstName, dta.test$LastName, dta.test$DOB), paste0(dta.test$FirstName, dta.test$LastName, dta.test$DOB))
在您的真实数据中,我希望person id不是那么简单(不仅仅是一个整数),并且不按数字顺序运行,例如
dta.test$Person_id <- paste0(LETTERS[1:11],1:11)
给予:
Person_id FirstName LastName DOB Actual_id
1 A1 John Smith 2001-01-01 A1
2 B2 James Jones 2002-01-01 B2
3 C3 John Jones 2003-01-01 C3
4 D4 Alex Jones 2004-01-01 D4
5 E5 Alexander Jones 2004-01-01 E5
6 F6 Jonathan Smith 2001-01-01 F6
7 G7 John Jones 2003-01-01 C3
8 H8 Alex Smith 2006-01-01 H8
9 I9 James Johnson 2006-01-01 I9
10 J10 John Smith 2001-01-01 A1
11 K11 John Smith 2009-01-01 K11
数据表解决方案可能是处理包含大量组的大型数据最快的解决方案:
library(data.table)
setDT(dta.test, key = c("FirstName", "LastName", "DOB"))
dta.test[, Actual_ID := min(Person_id, na.rm = TRUE), by = .(FirstName, LastName, DOB)]
数据表解决方案可能是处理包含大量组的大型数据最快的解决方案:
library(data.table)
setDT(dta.test, key = c("FirstName", "LastName", "DOB"))
dta.test[, Actual_ID := min(Person_id, na.rm = TRUE), by = .(FirstName, LastName, DOB)]
我刚把它打出来,+1完美。谢谢明显地更加清晰和简洁。但是性能的提高是什么?为什么?我只是在输入+1完美。谢谢明显地更加清晰和简洁。但是性能改进是什么?为什么?match
将进行第一场比赛,而不是最低限度的比赛。由于OP明确表示他们希望保留“最小的数值”,所以您应该首先进行排序。(为了进行公平比较,您可能应该在基准测试中包括排序时间,因为其他解决方案不需要排序输入。)注意,添加排序后,它仍然比第一次匹配快4倍,而不是最小值。由于OP明确表示他们希望保留“最小的数值”,所以您应该首先进行排序。(为了进行公平比较,您可能应该在基准测试中包括排序时间,因为其他解决方案不需要排序输入。)注意,添加排序后,排序速度仍然快4倍