代码之家  ›  专栏  ›  技术社区  ›  Aubrey Schrader

为什么R和switch函数将字符值视为函数?

  •  2
  • Aubrey Schrader  · 技术社区  · 2 年前

    我正在尝试使用sapply和switch将描述性名称应用于数据。我已经多次使用这种方法,但对于我最近的项目中的(只有一个!)列,它抛出了一个错误。我的最佳猜测是,即使该值被保存为字符串,否则该值将是R中的保留字。我在下面创建了一个可复制的示例。

    我的项目中的实际价值观与性别无关,可能有很多可能的选择。有人能告诉我如何使用sapply/switch来避免我的代码中有许多嵌套的ifelse语句吗?

    # create test data
    testdta <- as.data.frame(cbind(userid = c("1", "2", "3", "4"), gender = c("F", "M", "F", "M")))
    
    # sapply/switch works with strings that are not reserved words
    testdta$uiddescription <- sapply(testdta$userid, switch, "1" = "1 - first", "2" = "2 - second", "3+ - third or beyond")
    testdta
    
    # sapply/switch won't work when trying to interpret gender (possibly because F is reserved?)
    testdta$gdescription <- sapply(testdta$gender, switch, "F" = "F - female", "M" = "M - male")
    

    我收到的错误是“获取错误(as.character(FUN),mode=“function”,envir=envir): 未找到模式“function”的对象“F-female”。"

    3 回复  |  直到 2 年前
        1
  •  4
  •   joran    2 年前

    发生这种情况是因为中的部分参数匹配 sapply 。它读取 "F" = 作为 FUN 中的论点 sapply 。如果你很明确 FUN = switch 它会起作用的。

        2
  •  3
  •   r2evans    2 年前

    我认为@joran的回答解决了为什么 "F" 将引发问题。

    然而使用 sapply / switch 出于这个目的,这可能不是做你正在做的事情的最有效的方式(请参阅下面的基准,看看它们有多不同)。

    字典查找

    vec <- c("F" = "F - female", "M" = "M - male")
    vec[testdta$gender]
    #            F            M            F            M 
    # "F - female"   "M - male" "F - female"   "M - male" 
    

    合并/联接

    genders <- data.frame(gender=c("F", "M"), gender2=c("F - female", "M - male"))
    merge(testdta, genders, by="gender", all.x=TRUE)
    #   gender userid    gender2
    # 1      F      1 F - female
    # 2      F      3 F - female
    # 3      M      2   M - male
    # 4      M      4   M - male
    

    合并/联接的概念很好,但如果您不熟悉,可能会变得复杂,请参阅 How to join (merge) data frames (inner, outer, left, right) , What's the difference between INNER JOIN, LEFT JOIN, RIGHT JOIN and FULL JOIN? ,(data.table) Left join using data.table .

    基准

    bench::mark(
      sapply = sapply(testdta$gender, FUN=switch, "F" = "F - female", "M" = "M - male"),
      dict = vec[testdta$gender],
      join = merge(testdta, genders, by="gender", all.x=TRUE),
      check = FALSE)
    # # A tibble: 3 × 13
    #   expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc total_time result memory time                gc                   
    #   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>   <bch:tm> <list> <list> <list>              <list>               
    # 1 sapply       9.29µs     11µs    88949.        NA     8.90  9999     1    112.4ms <NULL> <NULL> <bench_tm [10,000]> <tibble [10,000 × 3]>
    # 2 dict         1.35µs   1.48µs   656398.        NA     0    10000     0     15.2ms <NULL> <NULL> <bench_tm [10,000]> <tibble [10,000 × 3]>
    # 3 join       165.12µs 194.11µs     5169.        NA     6.33  2450     3    473.9ms <NULL> <NULL> <bench_tm [2,453]>  <tibble [2,453 × 3]> 
    

    一个易于直观使用的列是 `itr/sec` ,或每秒迭代次数(越多越好)。

    当处理较大的数据(4行相当小)时,这种情况会发生一些变化,但即使这确实代表了您的真实数据,也会显示出明显的性能差异。

        3
  •  1
  •   Ben Bolker    2 年前

    更多选项:

    factor()具有级别&标签

    factor(testdta$gender, levels = c("F", "M"),
           labels = c("F - female", "M - male"))
    

    dplyr::case_when

    在这种情况下有点笨重(因为 testdta$gender == 必须重复)

    dplyr::case_when(testdta$gender == "F" ~ "F-female",
                     testdta$gender == "M" ~ "M-male")