替代 for() 循环以将非常大的数据框列条目与非常大的向量列表进行比较
Alternative to for() loop to compare very large data frame column entries to very large vector list
我正在尝试获取包含一列 email
地址的数据框 profiles
,并添加一个由每个电子邮件地址的可注册域部分组成的新列,domain
。
我单独创建了一个唯一的向量 registerable_domains
,在一个太复杂的过程中 运行 针对数据框中的每一行,其结果是一个必然的向量小于 profiles
数据框中的行数。然后我检查 registerable_domains
向量中的每个条目是否出现在 profiles
数据帧中每个 email
地址的末尾,并设置 domain
列条目匹配的数据框。
下面的代码是可复制的数据,您可以在 R 中复制粘贴并执行,每行都带有注释以解释它的作用。
for()
循环正是我想要做的:它在 profiles
数据框的 domain
列中创建适当的条目。问题在于,在此示例中,profiles
数据框有 12 行,而 registerable_domains
向量有 8 个条目。在实际数据集中,profiles
数据框有约 500,000 行,registerable_domains
向量有约 110,000 个条目。因此,虽然 for()
循环在小数据集上工作得很好,但我需要一种不同的方法来处理非常大的数据集(我估计这种方法需要大约 75 年才能完成完整数据设置!)。
非常感谢您帮助将此 for()
循环转换为大型数据集的时间实际操作。我查看了许多其他线程,但找不到解决此特定情况的任何答案(尽管解决了许多其他类似但不同的情况)。谢谢!
# Data frame consisting of a column of 12 emails, and a column of 12 NA entries:
email <- c( "john@doe.com",
"mary@smith.co.uk",
"peter@microsoft.com",
"jane@admins.microsoft.com",
"luke@star.wars.com",
"leia@star.wars.com",
"yoda@masters.star.wars.com",
"grandma@bletchly.ww2.wars.com",
"searchfor@janedoe.com",
"fan@mail.starwars.com",
"city@toronto.ca",
"area@toronto.canada.ca");
domain <- c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA);
profiles <- data.frame(email, domain);
profiles; # See what the initial data frame looks like
# email domain
# 1 john@doe.com NA
# 2 mary@smith.co.uk NA
# 3 peter@microsoft.com NA
# 4 jane@admins.microsoft.com NA
# 5 luke@star.wars.com NA
# 6 leia@star.wars.com NA
# 7 yoda@masters.star.wars.com NA
# 8 grandma@bletchly.ww2.wars.com NA
# 9 searchfor@janedoe.com NA
# 10 fan@mail.starwars.com NA
# 11 city@toronto.ca NA
# 12 area@toronto.canada.ca NA
# Vector consisting of email addresses stripped to registerable domain component only, created through a separate process that is too complex to run on each row entry:
registerable_domains <- c( "doe.com",
"smith.co.uk",
"microsoft.com",
"wars.com",
"janedoe.com",
"starwars.com",
"toronto.ca",
"canada.ca");
# Credit to Nick Kennedy for his help with this original solution (http://whosebug.com/users/4998761/nick-kennedy)
for (domains in registerable_domains) { # Iterate through each of the registerable domains
domains_pattern <- paste("[.@]", domains, "$", sep=""); # Add regex characters to ensure that it's only the end part to deal with nested domain names
found <- grepl(domains_pattern, profiles$email, ignore.case=TRUE, perl=TRUE); # Grep for the current domain pattern in all of the emails and build a boolean table for entry locations
profiles[which(found & is.na(profiles$domain)), "domain"] <- domains; # Modify profile data table at TRUE entry locations not yet set
}
profiles; # Expected and desired outcome:
# email domain
# 1 john@doe.com doe.com
# 2 mary@smith.co.uk smith.co.uk
# 3 peter@microsoft.com microsoft.com
# 4 jane@admins.microsoft.com microsoft.com
# 5 luke@star.wars.com wars.com
# 6 leia@star.wars.com wars.com
# 7 yoda@masters.star.wars.com wars.com
# 8 grandma@bletchly.ww2.wars.com wars.com
# 9 searchfor@janedoe.com janedoe.com
# 10 fan@mail.starwars.com starwars.com
# 11 city@toronto.ca toronto.ca
# 12 area@toronto.canada.ca canada.ca
这是一个使用dplyr
的解决方案
library(dplyr)
person <- data_frame(Email = email) %>%
mutate(Domain = gsub("^.*@", "", Email)) # everything upto the last @
domain <- person %>%
select(Domain) %>% # select the Domain variable
distinct() %>% # keep only unique rows
mutate(Original = Domain) # copy Domain into Original
extra <- domain %>%
mutate(Domain = gsub("^[[:alnum:]]*\.", "", Domain)) %>% # remove all alphanumeric characters upto the first point and overwrite Domain
filter(grepl("\.", Domain)) # keep only observations where domain contains at least one point
while (nrow(extra) > 0){
domain <- bind_rows(domain, extra) #add the rows from extra to domain
extra <- extra %>%
mutate(Domain = gsub("^[[:alnum:]]*\.", "", Domain)) %>%
filter(grepl("\.", Domain))
}
register <- data_frame(Domain = registerable_domains)
register %>%
inner_join(domain, by = "Domain") %>% #join the two table on a common Domain
inner_join(person, by = c("Original" = "Domain")) # join the resulting table to person where result.Original = person.Domain
不确定这是否有帮助,因为我完全改变了 for 循环的理念及其作用。另外,我没有意识到您是否真的需要可注册域。但是,我的想法是不要使用可注册域的列表,而是使用这些域具有的模式并将它们应用到您的电子邮件列表中。
例如,如果域以 com
或 ca
结尾,那么您将保留这部分,而左侧的内容,例如 searchfor@janedoe.com
变为 janedoe.com
。如果域以 uk
结尾,那么您需要这部分,还需要 co
以及之前的部分。
如果您设法发现这些模式,您可以使用 if-else 规则创建一个简单的函数并执行类似
的操作
x = c("luke@star.wars.com",
"area@toronto.canada.ca",
"mary@smith.co.uk")
dt = data.frame(x, stringsAsFactors = F)
dt
# x
# 1 luke@star.wars.com
# 2 area@toronto.canada.ca
# 3 mary@smith.co.uk
ff = function(x){
x = strsplit(x, split = "[[:punct:]]")[[1]]
ifelse(x[length(x)] %in% c("com","ca"),
paste(x[(length(x)-1):length(x)], collapse = "."),
paste(x[(length(x)-2):length(x)], collapse = "."))}
dt$v = sapply(dt$x, ff)
dt
# x v
# 1 luke@star.wars.com wars.com
# 2 area@toronto.canada.ca canada.ca
# 3 mary@smith.co.uk smith.co.uk
我认为您可以通过追求简单的成果并从 for
循环中取出一些易于矢量化的操作来显着减少您的时间。
profiles <- profiles %>% mutate(test_domains = sub(".*@", "", email))
很简单,只需为您提供一个新列供您使用,而无需在每次迭代中花费时间。
for (d in registerable_domains){
profiles$domain[d == profiles$test_domains] <- d
}
将进行直接匹配,并且应该只为那些仍然具有 NA
的行留下您现在的昂贵循环,即
profiles[is.na(profiles$domain)]
这将是一个真子集。我不知道这能为你节省多少,我现在必须走了。我会return到这个。感谢您用数据提出一个写得很好的问题。
我正在尝试获取包含一列 email
地址的数据框 profiles
,并添加一个由每个电子邮件地址的可注册域部分组成的新列,domain
。
我单独创建了一个唯一的向量 registerable_domains
,在一个太复杂的过程中 运行 针对数据框中的每一行,其结果是一个必然的向量小于 profiles
数据框中的行数。然后我检查 registerable_domains
向量中的每个条目是否出现在 profiles
数据帧中每个 email
地址的末尾,并设置 domain
列条目匹配的数据框。
下面的代码是可复制的数据,您可以在 R 中复制粘贴并执行,每行都带有注释以解释它的作用。
for()
循环正是我想要做的:它在 profiles
数据框的 domain
列中创建适当的条目。问题在于,在此示例中,profiles
数据框有 12 行,而 registerable_domains
向量有 8 个条目。在实际数据集中,profiles
数据框有约 500,000 行,registerable_domains
向量有约 110,000 个条目。因此,虽然 for()
循环在小数据集上工作得很好,但我需要一种不同的方法来处理非常大的数据集(我估计这种方法需要大约 75 年才能完成完整数据设置!)。
非常感谢您帮助将此 for()
循环转换为大型数据集的时间实际操作。我查看了许多其他线程,但找不到解决此特定情况的任何答案(尽管解决了许多其他类似但不同的情况)。谢谢!
# Data frame consisting of a column of 12 emails, and a column of 12 NA entries:
email <- c( "john@doe.com",
"mary@smith.co.uk",
"peter@microsoft.com",
"jane@admins.microsoft.com",
"luke@star.wars.com",
"leia@star.wars.com",
"yoda@masters.star.wars.com",
"grandma@bletchly.ww2.wars.com",
"searchfor@janedoe.com",
"fan@mail.starwars.com",
"city@toronto.ca",
"area@toronto.canada.ca");
domain <- c(NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA);
profiles <- data.frame(email, domain);
profiles; # See what the initial data frame looks like
# email domain
# 1 john@doe.com NA
# 2 mary@smith.co.uk NA
# 3 peter@microsoft.com NA
# 4 jane@admins.microsoft.com NA
# 5 luke@star.wars.com NA
# 6 leia@star.wars.com NA
# 7 yoda@masters.star.wars.com NA
# 8 grandma@bletchly.ww2.wars.com NA
# 9 searchfor@janedoe.com NA
# 10 fan@mail.starwars.com NA
# 11 city@toronto.ca NA
# 12 area@toronto.canada.ca NA
# Vector consisting of email addresses stripped to registerable domain component only, created through a separate process that is too complex to run on each row entry:
registerable_domains <- c( "doe.com",
"smith.co.uk",
"microsoft.com",
"wars.com",
"janedoe.com",
"starwars.com",
"toronto.ca",
"canada.ca");
# Credit to Nick Kennedy for his help with this original solution (http://whosebug.com/users/4998761/nick-kennedy)
for (domains in registerable_domains) { # Iterate through each of the registerable domains
domains_pattern <- paste("[.@]", domains, "$", sep=""); # Add regex characters to ensure that it's only the end part to deal with nested domain names
found <- grepl(domains_pattern, profiles$email, ignore.case=TRUE, perl=TRUE); # Grep for the current domain pattern in all of the emails and build a boolean table for entry locations
profiles[which(found & is.na(profiles$domain)), "domain"] <- domains; # Modify profile data table at TRUE entry locations not yet set
}
profiles; # Expected and desired outcome:
# email domain
# 1 john@doe.com doe.com
# 2 mary@smith.co.uk smith.co.uk
# 3 peter@microsoft.com microsoft.com
# 4 jane@admins.microsoft.com microsoft.com
# 5 luke@star.wars.com wars.com
# 6 leia@star.wars.com wars.com
# 7 yoda@masters.star.wars.com wars.com
# 8 grandma@bletchly.ww2.wars.com wars.com
# 9 searchfor@janedoe.com janedoe.com
# 10 fan@mail.starwars.com starwars.com
# 11 city@toronto.ca toronto.ca
# 12 area@toronto.canada.ca canada.ca
这是一个使用dplyr
library(dplyr)
person <- data_frame(Email = email) %>%
mutate(Domain = gsub("^.*@", "", Email)) # everything upto the last @
domain <- person %>%
select(Domain) %>% # select the Domain variable
distinct() %>% # keep only unique rows
mutate(Original = Domain) # copy Domain into Original
extra <- domain %>%
mutate(Domain = gsub("^[[:alnum:]]*\.", "", Domain)) %>% # remove all alphanumeric characters upto the first point and overwrite Domain
filter(grepl("\.", Domain)) # keep only observations where domain contains at least one point
while (nrow(extra) > 0){
domain <- bind_rows(domain, extra) #add the rows from extra to domain
extra <- extra %>%
mutate(Domain = gsub("^[[:alnum:]]*\.", "", Domain)) %>%
filter(grepl("\.", Domain))
}
register <- data_frame(Domain = registerable_domains)
register %>%
inner_join(domain, by = "Domain") %>% #join the two table on a common Domain
inner_join(person, by = c("Original" = "Domain")) # join the resulting table to person where result.Original = person.Domain
不确定这是否有帮助,因为我完全改变了 for 循环的理念及其作用。另外,我没有意识到您是否真的需要可注册域。但是,我的想法是不要使用可注册域的列表,而是使用这些域具有的模式并将它们应用到您的电子邮件列表中。
例如,如果域以 com
或 ca
结尾,那么您将保留这部分,而左侧的内容,例如 searchfor@janedoe.com
变为 janedoe.com
。如果域以 uk
结尾,那么您需要这部分,还需要 co
以及之前的部分。
如果您设法发现这些模式,您可以使用 if-else 规则创建一个简单的函数并执行类似
的操作x = c("luke@star.wars.com",
"area@toronto.canada.ca",
"mary@smith.co.uk")
dt = data.frame(x, stringsAsFactors = F)
dt
# x
# 1 luke@star.wars.com
# 2 area@toronto.canada.ca
# 3 mary@smith.co.uk
ff = function(x){
x = strsplit(x, split = "[[:punct:]]")[[1]]
ifelse(x[length(x)] %in% c("com","ca"),
paste(x[(length(x)-1):length(x)], collapse = "."),
paste(x[(length(x)-2):length(x)], collapse = "."))}
dt$v = sapply(dt$x, ff)
dt
# x v
# 1 luke@star.wars.com wars.com
# 2 area@toronto.canada.ca canada.ca
# 3 mary@smith.co.uk smith.co.uk
我认为您可以通过追求简单的成果并从 for
循环中取出一些易于矢量化的操作来显着减少您的时间。
profiles <- profiles %>% mutate(test_domains = sub(".*@", "", email))
很简单,只需为您提供一个新列供您使用,而无需在每次迭代中花费时间。
for (d in registerable_domains){
profiles$domain[d == profiles$test_domains] <- d
}
将进行直接匹配,并且应该只为那些仍然具有 NA
的行留下您现在的昂贵循环,即
profiles[is.na(profiles$domain)]
这将是一个真子集。我不知道这能为你节省多少,我现在必须走了。我会return到这个。感谢您用数据提出一个写得很好的问题。