【问题标题】:How to do vlookup and fill down (like in Excel) in R?如何在 R 中进行 vlookup 和填写(如在 Excel 中)?
【发布时间】:2013-02-24 13:05:51
【问题描述】:

我有一个大约 105000 行和 30 列的数据集。我有一个分类变量,我想将它分配给一个数字。在 Excel 中,我可能会使用 VLOOKUP 并填充。

我将如何在R 中做同样的事情?

本质上,我有一个HouseType 变量,我需要计算HouseTypeNo。以下是一些示例数据:

HouseType HouseTypeNo
Semi            1
Single          2
Row             3
Single          2
Apartment       4
Apartment       4
Row             3

【问题讨论】:

    标签: r lookup


    【解决方案1】:

    开始于:

    houses <- read.table(text="Semi            1
    Single          2
    Row             3
    Single          2
    Apartment       4
    Apartment       4
    Row             3",col.names=c("HouseType","HouseTypeNo"))
    

    ...你可以使用

    as.numeric(factor(houses$HouseType))
    

    ... 为每种房屋类型指定一个唯一编号。你可以在这里看到结果:

    > houses2 <- data.frame(houses,as.numeric(factor(houses$HouseType)))
    > houses2
      HouseType HouseTypeNo as.numeric.factor.houses.HouseType..
    1      Semi           1                                    3
    2    Single           2                                    4
    3       Row           3                                    2
    4    Single           2                                    4
    5 Apartment           4                                    1
    6 Apartment           4                                    1
    7       Row           3                                    2
    

    ...所以你最终会在行上得到不同的数字(因为这些因素是按字母顺序排列的)但模式相同。

    (编辑:这个答案中的剩余文本实际上是多余的。我突然想到要检查,结果发现read.table() 在首先将其读入数据框时已经将houses$HouseType 作为一个因素)。

    但是,将 HouseType 转换为一个因子可能会更好,这会给您带来与 HouseTypeNo 相同的所有好处,但会更容易解释,因为房屋类型是命名而不是编号,例如:

    > houses3 <- houses
    > houses3$HouseType <- factor(houses3$HouseType)
    > houses3
      HouseType HouseTypeNo
    1      Semi           1
    2    Single           2
    3       Row           3
    4    Single           2
    5 Apartment           4
    6 Apartment           4
    7       Row           3
    > levels(houses3$HouseType)
    [1] "Apartment" "Row"       "Semi"      "Single"  
    

    【讨论】:

      【解决方案2】:

      如果我正确理解您的问题,这里有四种方法可以做相当于 Excel 的 VLOOKUP 并使用 R 填写:

      # load sample data from Q
      hous <- read.table(header = TRUE, 
                         stringsAsFactors = FALSE, 
      text="HouseType HouseTypeNo
      Semi            1
      Single          2
      Row             3
      Single          2
      Apartment       4
      Apartment       4
      Row             3")
      
      # create a toy large table with a 'HouseType' column 
      # but no 'HouseTypeNo' column (yet)
      largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 1000, replace = TRUE)), stringsAsFactors = FALSE)
      
      # create a lookup table to get the numbers to fill
      # the large table
      lookup <- unique(hous)
        HouseType HouseTypeNo
      1      Semi           1
      2    Single           2
      3       Row           3
      5 Apartment           4
      

      以下是使用lookup 表中的值填充largetable 中的HouseTypeNo 的四种方法:

      首先使用merge 在基数中:

      # 1. using base 
      base1 <- (merge(lookup, largetable, by = 'HouseType'))
      

      在基中使用命名向量的第二种方法:

      # 2. using base and a named vector
      housenames <- as.numeric(1:length(unique(hous$HouseType)))
      names(housenames) <- unique(hous$HouseType)
      
      base2 <- data.frame(HouseType = largetable$HouseType,
                          HouseTypeNo = (housenames[largetable$HouseType]))
      

      第三,使用plyr包:

      # 3. using the plyr package
      library(plyr)
      plyr1 <- join(largetable, lookup, by = "HouseType")
      

      第四,使用sqldf

      # 4. using the sqldf package
      library(sqldf)
      sqldf1 <- sqldf("SELECT largetable.HouseType, lookup.HouseTypeNo
      FROM largetable
      INNER JOIN lookup
      ON largetable.HouseType = lookup.HouseType")
      

      如果largetable 中的某些房屋类型可能在lookup 中不存在,则将使用左连接:

      sqldf("select * from largetable left join lookup using (HouseType)")
      

      还需要对其他解决方案进行相应的更改。

      这是你想做的吗?让我知道你喜欢哪种方法,我会添加评论。

      【讨论】:

      • 我意识到这已经很晚了,但感谢您的帮助。我尝试了第一种和第二种方法。他们俩都工作得很好。再次感谢您回答问题!
      • 不客气。如果它回答了您的问题,您可以通过单击左上角箭头下方的勾号来表明这一点。这将有助于其他有相同问题的人。
      • 我认为解决方案 #2 仅起作用,因为在您的示例中,唯一值恰好按递增顺序排列(=第一个唯一名称是 1,第二个唯一名称是 2,依此类推)。如果您在 第二行 'HousType=ECII' 中添加 'hous' ,则 HousTypeNo='​​17' 查找会出错。
      • @ECII 请继续添加您的答案以说明问题并显示您的解决方案
      • 很棒的帖子。感谢分享! #4 非常适合我的应用程序...加入两个非常大的 400MB 表。
      【解决方案3】:

      我也喜欢使用qdapTools::lookup 或速记二元运算符%l%。它的工作原理与 Excel vlookup 相同,但它接受与列号相反的名称参数

      ## Replicate Ben's data:
      hous <- structure(list(HouseType = c("Semi", "Single", "Row", "Single", 
          "Apartment", "Apartment", "Row"), HouseTypeNo = c(1L, 2L, 3L, 
          2L, 4L, 4L, 3L)), .Names = c("HouseType", "HouseTypeNo"), 
          class = "data.frame", row.names = c(NA, -7L))
      
      
      largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 
          1000, replace = TRUE)), stringsAsFactors = FALSE)
      
      
      ## It's this simple:
      library(qdapTools)
      largetable[, 1] %l% hous
      

      【讨论】:

        【解决方案4】:

        Solution #2 @Ben 的答案在其他更通用的示例中不可重现。它恰好在示例中给出了正确的查找,因为 houses 中唯一的 HouseType 以递增的顺序出现。试试这个:

        hous <- read.table(header = TRUE,   stringsAsFactors = FALSE,   text="HouseType HouseTypeNo
          Semi            1
          ECIIsHome       17
          Single          2
          Row             3
          Single          2
          Apartment       4
          Apartment       4
          Row             3")
        
        largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 1000, replace = TRUE)), stringsAsFactors = FALSE)
        lookup <- unique(hous)
        

        Bens 解决方案#2 给出

        housenames <- as.numeric(1:length(unique(hous$HouseType)))
        names(housenames) <- unique(hous$HouseType)
        base2 <- data.frame(HouseType = largetable$HouseType,
                            HouseTypeNo = (housenames[largetable$HouseType]))
        

        什么时候

        unique(base2$HouseTypeNo[ base2$HouseType=="ECIIsHome" ])
        [1] 2
        

        当正确答案是查找表中的 17 时

        正确的做法是

         hous <- read.table(header = TRUE,   stringsAsFactors = FALSE,   text="HouseType HouseTypeNo
              Semi            1
              ECIIsHome       17
              Single          2
              Row             3
              Single          2
              Apartment       4
              Apartment       4
              Row             3")
        
        largetable <- data.frame(HouseType = as.character(sample(unique(hous$HouseType), 1000, replace = TRUE)), stringsAsFactors = FALSE)
        
        housenames <- tapply(hous$HouseTypeNo, hous$HouseType, unique)
        base2 <- data.frame(HouseType = largetable$HouseType,
          HouseTypeNo = (housenames[largetable$HouseType]))
        

        现在查找已正确执行

        unique(base2$HouseTypeNo[ base2$HouseType=="ECIIsHome" ])
        ECIIsHome 
               17
        

        我尝试编辑 Bens 的答案,但由于我无法理解的原因被拒绝。

        【讨论】:

          【解决方案5】:

          我觉得你也可以用match():

          largetable$HouseTypeNo <- with(lookup,
                               HouseTypeNo[match(largetable$HouseType,
                                                 HouseType)])
          

          如果我打乱lookup 的顺序,这仍然有效。

          【讨论】:

            【解决方案6】:

            您可以使用 plyr 包中的 mapvalues()

            初始数据:

            dat <- data.frame(HouseType = c("Semi", "Single", "Row", "Single", "Apartment", "Apartment", "Row"))
            
            > dat
              HouseType
            1      Semi
            2    Single
            3       Row
            4    Single
            5 Apartment
            6 Apartment
            7       Row
            

            查找/人行横道表:

            lookup <- data.frame(type_text = c("Semi", "Single", "Row", "Apartment"), type_num = c(1, 2, 3, 4))
            > lookup
              type_text type_num
            1      Semi        1
            2    Single        2
            3       Row        3
            4 Apartment        4
            

            创建新变量:

            dat$house_type_num <- plyr::mapvalues(dat$HouseType, from = lookup$type_text, to = lookup$type_num)
            

            或者对于简单的替换,您可以跳过创建长查找表并直接一步完成:

            dat$house_type_num <- plyr::mapvalues(dat$HouseType,
                                                  from = c("Semi", "Single", "Row", "Apartment"),
                                                  to = c(1, 2, 3, 4))
            

            结果:

            > dat
              HouseType house_type_num
            1      Semi              1
            2    Single              2
            3       Row              3
            4    Single              2
            5 Apartment              4
            6 Apartment              4
            7       Row              3
            

            【讨论】:

              【解决方案7】:

              发帖人没有询问是否要查找值 exact=FALSE,但我将其添加为我自己和可能其他人参考的答案。

              如果您要查找分类值,请使用其他答案。

              Excel 的 vlookup 还允许您将数值与第四个参数 (1) match=TRUE 近似匹配。我认为match=TRUE 就像在温度计上查找值一样。默认值为 FALSE,非常适合分类值。

              如果你想近似匹配(执行查找),R 有一个名为findInterval 的函数,它(顾名思义)会找到包含你的连续数值的区间/bin。

              但是,假设您想要 findInterval 获取多个值。您可以编写一个循环或使用一个应用函数。但是,我发现采用 DIY 矢量化方法更有效。

              假设您有一个由 x 和 y 索引的值网格:

              grid <- list(x = c(-87.727, -87.723, -87.719, -87.715, -87.711), 
                           y = c(41.836, 41.839, 41.843, 41.847, 41.851), 
                           z = (matrix(data = c(-3.428, -3.722, -3.061, -2.554, -2.362, 
                                                -3.034, -3.925, -3.639, -3.357, -3.283, 
                                                -0.152, -1.688, -2.765, -3.084, -2.742, 
                                                 1.973,  1.193, -0.354, -1.682, -1.803, 
                                                 0.998,  2.863,  3.224,  1.541, -0.044), 
                                       nrow = 5, ncol = 5)))
              

              并且您有一些值要通过 x 和 y 查找:

              df <- data.frame(x = c(-87.723, -87.712, -87.726, -87.719, -87.722, -87.722), 
                               y = c(41.84, 41.842, 41.844, 41.849, 41.838, 41.842), 
                               id = c("a", "b", "c", "d", "e", "f")
              

              这是可视化的示例:

              contour(grid)
              points(df$x, df$y, pch=df$id, col="blue", cex=1.2)
              

              您可以使用这种类型的公式找到 x 间隔和 y 间隔:

              xrng <- range(grid$x)
              xbins <- length(grid$x) -1
              yrng <- range(grid$y)
              ybins <- length(grid$y) -1
              df$ix <- trunc( (df$x - min(xrng)) / diff(xrng) * (xbins)) + 1
              df$iy <- trunc( (df$y - min(yrng)) / diff(yrng) * (ybins)) + 1
              

              您可以更进一步,对grid 中的 z 值执行(简单的)插值,如下所示:

              df$z <- with(df, (grid$z[cbind(ix, iy)] + 
                                    grid$z[cbind(ix + 1, iy)] +
                                    grid$z[cbind(ix, iy + 1)] + 
                                    grid$z[cbind(ix + 1, iy + 1)]) / 4)
              

              这为您提供了这些值:

              contour(grid, xlim = range(c(grid$x, df$x)), ylim = range(c(grid$y, df$y)))
              points(df$x, df$y, pch=df$id, col="blue", cex=1.2)
              text(df$x + .001, df$y, lab=round(df$z, 2), col="blue", cex=1)
              

              df
              #         x      y id ix iy        z
              # 1 -87.723 41.840  a  2  2 -3.00425
              # 2 -87.712 41.842  b  4  2 -3.11650
              # 3 -87.726 41.844  c  1  3  0.33150
              # 4 -87.719 41.849  d  3  4  0.68225
              # 6 -87.722 41.838  e  2  1 -3.58675
              # 7 -87.722 41.842  f  2  2 -3.00425
              

              注意 ix 和 iy 也可以通过使用 findInterval 的循环找到,例如这是第二行的一个示例

              findInterval(df$x[2], grid$x)
              # 4
              findInterval(df$y[2], grid$y)
              # 2
              

              df[2] 中的ixiy 匹配

              脚注: (1) vlookup 的第四个参数以前称为“match”,但在他们引入了功能区后,它被重命名为“[range_lookup]”。

              【讨论】:

                【解决方案8】:

                使用merge 与Excel 中的查找不同,因为如果在查找表中未强制执行主键约束,它可能会复制(乘以)您的数据,或者如果您不使用all.x = T,则可能会减少记录数。

                为了确保您不会遇到麻烦并安全查找,我建议了两种策略。

                第一个是检查查找键中的重复行数:

                safeLookup <- function(data, lookup, by, select = setdiff(colnames(lookup), by)) {
                  # Merges data to lookup making sure that the number of rows does not change.
                  stopifnot(sum(duplicated(lookup[, by])) == 0)
                  res <- merge(data, lookup[, c(by, select)], by = by, all.x = T)
                  return (res)
                }
                

                这将迫使您在使用查找数据集之前对其进行重复数据删除:

                baseSafe <- safeLookup(largetable, house.ids, by = "HouseType")
                # Error: sum(duplicated(lookup[, by])) == 0 is not TRUE 
                
                baseSafe<- safeLookup(largetable, unique(house.ids), by = "HouseType")
                head(baseSafe)
                # HouseType HouseTypeNo
                # 1 Apartment           4
                # 2 Apartment           4
                # ...
                

                第二个选项是通过从查找数据集中获取第一个匹配值来重现 Excel 行为:

                firstLookup <- function(data, lookup, by, select = setdiff(colnames(lookup), by)) {
                  # Merges data to lookup using first row per unique combination in by.
                  unique.lookup <- lookup[!duplicated(lookup[, by]), ]
                  res <- merge(data, unique.lookup[, c(by, select)], by = by, all.x = T)
                  return (res)
                }
                
                baseFirst <- firstLookup(largetable, house.ids, by = "HouseType")
                

                这些函数与lookup 略有不同,因为它们添加了多个列。

                【讨论】:

                  【解决方案9】:

                  lookup 包可以在这里使用:

                  library(lookup)
                  # reference data
                  hous <- data.frame(HouseType=c("Semi","Single","Row","Single","Apartment","Apartment","Row"),
                                     HouseTypeNo=c(1,2,3,2,4,4,3))
                  # new large data with HouseType but no HouseTypeNo
                  largetable <- data.frame(HouseType = sample(unique(hous$HouseType), 1000, replace = TRUE))
                  
                  # vector approach
                  largetable$num1 <- lookup(largetable$HouseType, hous$HouseType, hous$HouseTypeNo)
                  # dataframe approach
                  largetable$num2 <- vlookup(largetable$HouseType, hous, "HouseType", "HouseTypeNo")
                  
                  head(largetable)
                  #   HouseType num1 num2
                  # 1      Semi    1    1
                  # 2      Semi    1    1
                  # 3 Apartment    4    4
                  # 4      Semi    1    1
                  # 5    Single    2    2
                  # 6    Single    2    2
                  

                  【讨论】:

                  • 此解决方案最接近 Excel 实现。
                  猜你喜欢
                  • 2012-01-13
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-05-11
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多