【问题标题】:How to prevent rows with duplicated indices / keys to be appended to a data.frame?如何防止将具有重复索引/键的行附加到 data.frame?
【发布时间】:2016-03-20 10:50:35
【问题描述】:

我有两个变量(“ManufactererId”和“ProductId”)的组合构成唯一键/标识符的数据。数据如下所示:

my.data <- data.frame(ManufactererId = c(1, 1, 2, 2),
                      ProductId = c(1, 2, 1, 7),
                      Price = c(12.99, 149.00, 0.99, 3.99))
my.data
#   ManufactererId ProductId  Price
# 1              1         1  12.99
# 2              1         2 149.00
# 3              2         1   0.99
# 4              2         7   3.99

我想确保我不会意外添加另一行,其中包含一对 ManufactererId - ProductId 等于表中已经存在的值(例如数据库表上的唯一约束)。

也就是说,如果我尝试将 ManufactererId = 2 和 ProductId = 7 的行添加到我的数据框中:

my.data <- rbind(my.data, data.frame(ManufactererId = 2, ProductId = 7, Price = 120.00))

...它应该会因错误而失败。如何实现?

或者我应该使用不同的数据类型?

【问题讨论】:

    标签: r dataframe data.table uniqueidentifier unique-key


    【解决方案1】:

    1) zoo 这是否方便取决于您要执行的操作,但 zoo 对象具有唯一索引。我们可以通过将两个 Id 列粘贴在一起来构造文本索引。

    library(zoo)
    z <- with(my.data, zoo(Price, paste(ManufactererId, ProductId)))
    
    z <- c(z, zoo(90, "1 1")) # Error, not appended
    z <- c(z, zoo(90, "1 3")) # OK
    

    请注意,动物园对象的数据部分可以是如上所示的矢量,也可以是矩阵,以防数据中包含的不仅仅是价格。

    2) SQLite 这可以使用许多数据库中的任何一个来完成,但我们将在这里使用 SQLite。首先,我们在 SQLite 数据库中创建一个具有唯一索引的表,然后插入行。

    library(RSQLite)
    
    con <- dbConnect(SQLite())
    dbWriteTable(con, "my", my.data, row.names = FALSE)
    dbGetQuery(con, "create unique index ix on my(ManufactererId, ProductId)")
    
    dbGetQuery(con, sprintf("insert into my values(%d, %d, %d)", 1, 1, 99)) # error
    dbGetQuery(con, sprintf("insert into my values(%d, %d, %d)", 1, 13, 90)) # OK
    

    【讨论】:

    • 添加了第二种方法。
    【解决方案2】:

    您可以这样做:keys 是您的唯一密钥

    append_save <- function(DF, to_be_appended, keys=c("ManufactererId", "ProductId")){
      if(ncol(DF) != ncol(to_be_appended) || !all(names(DF) %in% names(to_be_appended))){
        stop("must have the same columns")
      }
      if(nrow(merge(DF, to_be_appended, by=keys))==0){
        rbind(DF, to_be_appended)
      } else {
        stop("Trying to append douplicated indices")
      }
    }
    

    测试一下:

    to_be_appended = data.frame(ManufactererId=2,ProductId=17,Price=3.99)
    append_save(my.data, to_be_appended) # works
    to_be_appended_err = data.frame(ManufactererId=2,ProductId=7,Price=3.99)
    append_save(my.data, to_be_appended_err) # error
    

    如果您仅根据键列附加数据,您可以使用data.table,如下所示:

    append_save <- function(DF, to_be_appended, keys=c("ManufactererId", "ProductId")){
      if(!all(keys %in% names(to_be_appended))){
        stop("key-columns must be present")
      }
      if(nrow(data.table::merge(DF, to_be_appended, on=keys))==0){
        data.table::setDF(data.table::rbindlist(list(DF, to_be_appended), fill = TRUE))[]
      } else {
        stop("Trying to append douplicated indices")
      }
    }
    

    【讨论】:

      【解决方案3】:

      在基础 R 中执行此操作的一种方法是使用 environment 作为字典或类似哈希映射的对象。 my.dict

      首先,编写一些辅助函数

      make_key <- function(ManufactererId, ProductId)
        paste(ManufactererId, ProductId)
      
      set_value <- function(key, value, dict){
               ## checking here assures desired behavior 
               if(any(key %in% names(dict)))
                  stop("This key has been used")
               assign(key, value,  envir=dict)
      }
      

      然后,您可以生成类似的密钥

      keys <- make_key(my.data[[1]], my.data[[2]])
      

      要设置值,您需要更加小心

      # don't just do this as the first element is used by assign
      # set_value(keys, my.data[[3]], dict=my.dict)
      
      mapply(set_value, keys, my.data[[3]], MoreArgs = list(dict=my.dict))
      ls.str(my.dict) # better than str for environments
      # 1 1 :  num 13
      # 1 2 :  num 149
      # 2 1 :  num 0.99
      # 2 7 :  num 3.99
      
      set_value("1 1", 4, my.dict)
      # Error in set_value("1 1", 4, my.dict) : This key has been used
      

      【讨论】:

        【解决方案4】:

        rbind 不包括重复项的新数据的简单方法:

        library(data.table)
        my.data = data.table(ManufactererId = c(1, 1, 2, 2),
                             ProductId = c(1, 2, 1, 7),
                             Price = c(12.99, 149.00, 0.99, 3.99),
                             key = c("ManufactererId","ProductId"))
        x = my.data # my data will be called 'x'
        y = data.table(ManufactererId = 2, ProductId = 7, Price = 120.00)
        rbind(x, y[!x, on=key(x)])
        #   ManufactererId ProductId  Price
        #1:              1         1  12.99
        #2:              1         2 149.00
        #3:              2         1   0.99
        #4:              2         7   3.99
        

        虽然您不需要设置键,但只需将字符向量直接提供给on 参数即可。我认为值得使用仅反映我们对数据结构的业务期望的键。


        如果您想在这种情况下引发错误,可以使用以下命令:

        unique.rbind = function(x, y, by=key(x)) {
            if (nrow(x[y, nomatch=0L, on=by])) stop("duplicates in 'y'")
            rbind(x, y)
        }
        unique.rbind(x, y)
        # Error in unique.rbind(x, y) : duplicates in 'y'
        

        在出现错误时不会插入任何y 行。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2010-09-24
          • 1970-01-01
          • 2023-03-12
          • 2012-12-20
          • 2022-11-24
          • 2022-06-11
          • 1970-01-01
          相关资源
          最近更新 更多