【问题标题】:Mutate inside a function: object not found在函数内部发生变异:找不到对象
【发布时间】:2021-07-20 19:02:11
【问题描述】:

我有一个如下所示的简单数据集:

我想将价格变量转换为数字,据我所知,这需要首先清除 € 符号并将逗号更改为点。我已经为 price_new 变量手动完成了这项工作,它按预期工作:

 sp_merrel_df_clean <- sp_merrel_df_raw %>%
    mutate(
      price_new = sub(" €", "", price_new),
      price_new = sub(",", ".", price_new),
      price_new = as.numeric(price_new)
    )

但是,由于我是 R 新手,我想把它变成一个更通用的函数来练习。所以我使用了与上面完全相同的代码,但是用通用占位符替换了特定的输入 - 大概它会起作用,因为它是完全相同的代码......

  currency_to_numeric <- function(raw, clean, var) {
    clean <- raw %>%
      mutate(
        var = sub(" €", "", var),
        var = sub(",", ".", var),
        var = as.numeric(var))
  }

...但是,当调用时:

currency_to_numeric(raw = sp_merrel_df_raw, clean = sp_merrel_df_clean2, var = price_new)

我收到此错误:

Error: Problem with `mutate()` column `var`.
i `var = sub(" \200", "", var)`.
x object 'price_new' not found

抱歉,这可能是一个非常基本的问题,并提前感谢您!

【问题讨论】:

  • 你应该研究dplyr的程序化使用;我会从dplyr.tidyverse.org/articles/…开始。
  • 您好,欢迎您!您的问题是 var 参数。 R 按值传递,因此当您的currency_to_numeric() 函数接收到price_new 作为其var 时,它会检查price_new。由于变量price_new 被去上下文化——并且price_new 不存在于.GlobalEnvironment——R 找不到它。只有在sp_merrel_df_clean 的上下文中,并且price_newmasked variable,你才能折腾这样的变量名并期望R 将它们识别为属于“附近的”data.frame
  • 另外,由于(再次)R 是按值传递而不是按引用传递clean &lt;- raw %&gt;% 行将绝对没有任何作用。它既不会采用 reference 以某种方式作为参数 clean 传递的原始变量,也不会以某种方式改变该原始变量。相反,您必须简单地将raw %&gt;% 放在该行中,然后用currency_to_numeric()result 覆盖原始变量。
  • 我认为这里的问题是将参数传递给使用 dplyr 非标准评估的函数
  • @Greg 这是导致找不到对象错误的原因吗?

标签: r function dplyr


【解决方案1】:

下面,我包含了一个非常符合人体工程学的解决方案,它(几乎)无缝地模仿了dplyr 工作流程的熟悉感觉。我还花了一些时间来诊断和解决概念上的缺陷。

一个常见的混淆来源是 R 传递而不是引用。除了程序化dplyr 的细微差别之外,这一事实也是造成代码中两个概念性错误的原因。

为方便起见,我在此将您的sp_merrel_df_raw 转载为data.frame

structure(list(product_name = c("Merrell Riverbed 3", "Sapatilhas Montanha Merrell", "Merrell Moab Adventure", "Merrell Moab 2 Vent"),
               price_new = c("59,99 €", "149,99 €", "99,99 €", "79,99 €"),
               price_old = c("69,99 €", NA, NA, "99,99 €"),
               date = c(210720, 210720, 210720, 210720)),
          row.names = c(NA, -4L),
          class = "data.frame")

您不能“就地”修改

偶然地,我clarified 前段时间确实有这种困惑。假设你有一个简单的numeric 变量a 和一个简单的函数doubler()

x <- 2

doubler <- function(num) {
  num <- 2 * num
}

现在除了(不可见地)返回4 之外,只需运行doubler(x) 即可执行absolutely nothing。所发生的只是参数num 被传递 2,然后num4 覆盖在函数的范围内。但是,原始变量 x 保持不变:

doubler(x)

x
# [1] 2

为了修改x,我们必须用函数的结果覆盖它(&lt;-):

x <- doubler(x)

x
# [1] 4

类似地,当您运行 currency_to_numeric() 函数时

currency_to_numeric(raw = sp_merrel_df_raw, clean = sp_merrel_df_clean2, var = price_new)

它将接受sp_merrel_df_clean2,并将该 分配给它的clean 参数。之后发生的一切

    clean <- raw %>% # ...

只影响clean函数的范围内。说到底,sp_merrel_df_clean2 永远不会受到影响。

相反,需要这样的东西,用新值覆盖 sp_merrel_df_clean2

currency_to_numeric <- function(raw, ...) {
  # ...
}

sp_merrel_df_clean2 <- currency_to_numeric(raw = sp_merrel_df_raw, ...)

去上下文变量

dplyr documentation 中所述

环境变量是存在于环境中的“编程”变量

数据变量是存在于数据框中的“统计”变量。

现在数据变量在某些函数的上下文中被“屏蔽”了,尤其是在dplyr 中。当我们在sp_merrel_df_raw 上执行(比如说)mutate() 时,这种屏蔽让我们将sp_merrel_df_raw$price_new 列简单地称为price_new

sp_merrel_df_raw %>%
  mutate(
    price_new = sub(" €", "", price_new)
    # ...
  )

但是,当您运行 currency_to_numeric() 函数时

currency_to_numeric(raw = sp_merrel_df_raw, clean = sp_merrel_df_clean2, var = price_new)

var不会变成sp_merrel_df_raw$price_new变量本身

相反,R 在周围环境(此处为 .GlobalEnv)中寻找一些名为 price_newenv-variable,并尝试将其 分配给 @ 987654366@参数。自然,由于.GlobalEnv 中不存在这样的price_new 变量,因此没有这样的值,所以R 在尝试在mutate() 中使用该值时立即抛出错误:

Error: Problem with `mutate()` column `var`.
i `var = sub(" \200", "", var)`.
x object 'price_new' not found

此错误与您在任何其他不存在的变量上调用函数时所得到的错误相当:

doubler(num = nonexistent_variable)

# Error in doubler(num = nonexistent_variable) : 
#   object 'nonexistent_variable' not found

然而,即使price_new 作为一个典型的env-variable实际上在.GlobalEnv 中浮动,你仍然会得到一个错误。这是因为将price_new 传递给var 与“粘贴”“名称”price_new 不同

mutate(
        price_new = sub(" €", "", price_new),
        price_new = sub(",", ".", price_new),
        price_new = as.numeric(price_new))

“名称”var 曾经在哪里。

mutate(
        var = sub(" €", "", var),
        var = sub(",", ".", var),
        var = as.numeric(var))

解决方案

这是对currency_to_numeric () 的精巧改造,非常接近dplyr 的典型功能:

currency_to_numeric <- function(raw, ...) {
  raw %>%
    mutate(
      across(c(...), ~ sub(" €", "", .x)),
      across(c(...), ~ sub(",", ".", .x)),
      across(c(...), ~ as.numeric(.x))
      )
}

与几乎所有 R 函数一样,您仍必须将结果分配sp_merrel_df_clean2,但此解决方案将帮助您非常干净地完成此操作

sp_merrel_df_clean2 <- sp_merrel_df_raw %>%
  currency_to_numeric(price_new)

sp_merrel_df_clean2 的结果如下:

                 product_name price_new price_old   date
1          Merrell Riverbed 3     59.99   69,99 € 210720
2 Sapatilhas Montanha Merrell    149.99      <NA> 210720
3      Merrell Moab Adventure     99.99      <NA> 210720
4         Merrell Moab 2 Vent     79.99   99,99 € 210720

事实上,您可以同时定位任意数量的数据变量(如price_newprice_old

sp_merrel_df_clean2 <- sp_merrel_df_raw %>%
  currency_to_numeric(price_new, price_old)

一举隐藏所有货币列!

                 product_name price_new price_old   date
1          Merrell Riverbed 3     59.99     69.99 210720
2 Sapatilhas Montanha Merrell    149.99        NA 210720
3      Merrell Moab Adventure     99.99        NA 210720
4         Merrell Moab 2 Vent     79.99     99.99 210720

【讨论】:

    【解决方案2】:

    您可以使用{{}} ('curly-curly') 语法来传递不带引号的变量。

    library(tidyverse)
    
    currency_to_numeric <- function(df, col) {
      df %>% 
        mutate(
          price_new = sub(" €", "", {{col}}),
          price_new = sub(",", ".", {{col}}),
          price_new = as.numeric({{col}})
        )
    }
    
    currency_to_numeric(raw, price_new)
    
    # A tibble: 4 x 4
      product_name price_new price_old date      
      <chr>            <dbl> <chr>     <date>    
    1 a                 0.54 0,53 €    2021-07-19
    2 b                 0.41 1,3 €     2021-07-18
    3 c                 0.08 1,66 €    2021-07-17
    4 d                 0.35 0,25 €    2021-07-16
    

    示例数据

    
    product_name <- letters[1:4]
    price_new <- paste(round(abs(rnorm(4)), 2), "€") %>% str_replace(., "\\.", ",")
    price_old <- paste(round(abs(rnorm(4)), 2), "€") %>% str_replace(., "\\.", ",")
    date <- lubridate::today() - 1:4
    
    raw <- tibble(product_name, price_new, price_old, date)
    

    如果您想在同一命令中更新两个 price_ 列,您可以重写 currency_to_numeric 以对列进行操作,而不是对整个数据框进行操作。像这样:

    currency_to_numeric2 <- function(x) {
      updated = sub(" €", "", x)
      updated = sub(",", ".", updated)
      updated = as.numeric(updated)
    }
    
    raw %>% mutate(across(starts_with("price"), currency_to_numeric2))
    
    # A tibble: 4 x 4
      product_name price_new price_old date      
      <chr>            <dbl>     <dbl> <date>    
    1 a                 0.54      0.53 2021-07-19
    2 b                 0.41      1.3  2021-07-18
    3 c                 0.08      1.66 2021-07-17
    4 d                 0.35      0.25 2021-07-16
    

    【讨论】:

    • currency_to_numeric2() 的最后一个解决方案非常好——尤其是在允许多个变量的tidy-selection 方面——这是我在推广自己的currency_to_numeric() 之前的第一直觉。但是,第一个解决方案根本无法满足 OP 的意图。 OP 想要在raw 中找到并转换给定的data-variable var,从character(带有,)到numeric。然而,您的 currency_to_numeric()总是覆盖 price_new 特别是,并转换为var
    • 嘿@Greg - 感谢您的注意。但不确定我是否理解; OP 提供了代码并写了“它按预期工作”,并且该代码覆盖了price_new。第一个函数再现了预期的输出。如果您运行代码,您还可以看到原始的raw tibble 保持不变。
    • 好吧,OP提供的工作代码不是函数,当然也不是对参数raw进行操作的函数。相反,它是一个(正确的)dplyr 工作流,专门针对price_new 变量而编写。当 OP 用他们的 currency_to_numeric() 函数进行泛化时,他们的意图是将 any 数据集作为 raw 参数,转换 raw 中存在的 any var 变量,并通过引用将结果分配给clean(犯了错误)。要点是:OP 期望将price_old 传递给currency_to_numeric() 作为var,并转换price_old
    猜你喜欢
    • 2019-02-28
    • 2015-08-04
    • 1970-01-01
    • 2020-11-22
    • 1970-01-01
    • 2021-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多