【问题标题】:How to lookup items from one dataframe in another dataframe based on a custom 'matching' function如何根据自定义“匹配”功能从另一个数据框中的一个数据框中查找项目
【发布时间】:2020-09-22 08:56:24
【问题描述】:

我有两个数据框:sold1 和 sold。

他们都持有已售房价的数据,但来源不同。事实证明,即使是同一属性,数据也可能不匹配。

我想将 Sold1 中的每个元素与 Sold2 进行比较,并排除 Sold1 中已存在(重复)在 sold2 中的元素。

在这种情况下,对“重复”的测试将是以下各项的组合:

  1. Sold1.price 与 Sold2.price 相差 10% 以内
  2. Sold2.address 包含在 Sold1.address 中
  3. Sold2.dateOfSale 不早于 Sold1.dateOfSale

两个问题:

  1. 为什么在函数中访问“价格”字段时会更改类?
  2. 有没有更优雅的方法来做到这一点,而无需使用两次 apply(在两个大型数据帧上非常慢)?
sold1 <- data.frame(
  price = c(100000,150000,200000,250000,300000,400000),
  address = c("Widmore Road, Bromley", "River Quaggy Apartments", "Meadowcroft Way, Orwell, SG8","Freelands Road, Bromley","Nascot Street, London, W12","Priory Terrace, South Hampstead, NW6"),
  dateOfSale = c(as.Date("2020-01-01"),as.Date("2020-02-03"),as.Date("2020-03-05"),as.Date("2020-04-06"),as.Date("2020-05-08"),as.Date("2020-06-12"))
)

sold2 <- data.frame(
  price = c(100000,150000,210000,251000,300000),
  address = c("Random Road, Bromley", "Random2 Road Apartments", "Meadowcroft Way","Freelands Road","Random street London, W12"),
  dateOfSale = c(as.Date("2020-01-01"),as.Date("2020-04-03"),as.Date("2020-03-25"),as.Date("2020-04-26"),as.Date("2019-05-08"))
)

FLR_Check_Match <- function (s2, s1row) {
  checkRes = TRUE
  # Price within tolerance 
  checkRes = checkRes && as.numeric(s1row["price"]) >= as.numeric(s2["price"]) * .9 && as.numeric(s1row["price"]) <= as.numeric(s2["price"]) * 1.1
  # address close match
  checkRes = checkRes && grepl(s2["address"], s1row["address"], ignore.case = TRUE)
  # date of sale falls 7 weeks after date of sale in the sold1 (s1) data
  checkRes = checkRes && as.Date(s2["dateOfSale"]) >= as.Date(s1row["dateOfSale"])
  return(checkRes)
}

FCheck_Sold_Dups <- function(s1, s2) {
  print(class(s1["price"])) #character
  #For each element in s2, check whether there is a match to s1
  excV <- apply(s2, 1, FLR_Check_Match, s1)
  result <- any(excV)
  return(result)
}

sold1$exclude <- apply(sold1,1,FCheck_Sold_Dups,sold2)

【问题讨论】:

  • 代码对你有用吗?当我运行sold1$exclude &lt;- apply(sold1,1,FCheck_Sold_Dups,sold2) 它返回s2["price"] * 0.9 : non-numeric argument to binary operator
  • 不——这是问题之一。当使用 as.numeric() 进行转换时,该错误被消除。但是我不明白为什么在函数中访问向量的类/类型时会发生变化。
  • @Ronak,我已经修改以反映 as.numeric/as.Date。它现在有效,但同样的问题也适用。

标签: r


【解决方案1】:

1.为什么在函数中访问“价格”字段时会更改类?

apply 将数据帧强制转换为矩阵。

来自?apply

如果 X 不是数组,而是具有非 null 暗值的类的对象(例如数据框),则通过 as.matrix 尝试将其强制转换为数组

这也可以通过apply的源码验证:

apply
function (X, MARGIN, FUN, ...) 
{
    FUN <- match.fun(FUN)
    dl <- length(dim(X))
    if (!dl) 
        stop("dim(X) must have a positive length")
    if (is.object(X)) 
        X <- if (dl == 2L) 
            as.matrix(X) # <- here
        else as.array(X)
     .....
     .....

由于矩阵只能保存一种类型的数据,因此所有内容都更改为字符。因此,您必须在函数中再次将数据强制转换为数字。


2。有没有更优雅的方法来做到这一点而不使用两次应用(在两个大型数据帧上非常慢)

我们无法避免将sold1 的每一行与sold2 进行比较(可能是模糊匹配/加入?)所以如果您有非常大的数据,大多数答案都会很慢。这是一种可能更优雅、更快、更短的方法。我在 sold1sold2 数据帧的列名中添加了前缀以区分它们。

library(dplyr)

names(sold1) <- paste0('sold1_', names(sold1))
names(sold2) <- paste0('sold2_', names(sold2))

tidyr::crossing(sold1, sold2) %>%
        group_by(sold1_address) %>%
                  #Check if address matches
        summarise(address_check = any(str_detect(sold1_address, stringr::regex(sold2_address, ignore_case = TRUE))), 
                  #Check if price is in range
                  price_check = any(data.table::between(sold1_price, sold2_price * .9, sold2_price * 1.1)), 
                  #Check if date is in range
                  date_check = any(sold2_dateOfSale >= sold1_dateOfSale), 
                  #if all the three conditions satisfy
                  exclude = address_check & price_check & date_check) %>%
        select(sold1_address, exclude) %>%
        left_join(sold1, by = 'sold1_address')

# sold1_address                        exclude sold1_price sold1_dateOfSale
#  <chr>                                <lgl>         <dbl> <date>          
#1 Freelands Road, Bromley              TRUE         250000 2020-04-06      
#2 Meadowcroft Way, Orwell, SG8         TRUE         200000 2020-03-05      
#3 Nascot Street, London, W12           FALSE        300000 2020-05-08      
#4 Priory Terrace, South Hampstead, NW6 FALSE        400000 2020-06-12      
#5 River Quaggy Apartments              FALSE        150000 2020-02-03      
#6 Widmore Road, Bromley                FALSE        100000 2020-01-01      

请注意,为了清楚起见,我创建了 3 个单独的列 address_checkprice_checkdate_check,并将每个条件分开,它们不是必需的,如果需要,我们可以将这些条件合并为一个。

【讨论】:

  • 这很有趣。虽然不能让它工作。出现错误错误:无法对不存在的列进行子集化。 x 列 sold1_address 不存在。 'summarise' 似乎由于某种原因导致了一个单行表。
  • 这很奇怪。根据您提供的数据,它对我有用。您是否使用我帖子顶部的代码重命名了 sold1sold2 的列? names(sold1) &lt;- paste0('sold1_', names(sold1))names(sold2) &lt;- paste0('sold2_', names(sold2))
  • 是的,'summarise'后的结果是:address_check price_check date_check exclude TRUE TRUE TRUE TRUE。其他列已经消失,好像分组不适用。但是 'group_by' 之后的结果确实指定了 '# Groups: sold1_address [6]'。
  • 你也加载了plyr 吗?您可以尝试使用dplyr::summarise() 还是重新启动您的R 会话并仅加载dplyr
  • 很好 - 解决了它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-03-23
  • 1970-01-01
  • 1970-01-01
  • 2022-11-23
  • 1970-01-01
  • 1970-01-01
  • 2017-01-20
相关资源
最近更新 更多