【问题标题】:How do I write a function in r to do cacluations on a record?如何在 r 中编写一个函数来对记录进行计算?
【发布时间】:2020-11-09 03:01:10
【问题描述】:

在 C# 中,我习惯了数据集和当前记录的概念。 我可以很容易地编写一个复杂的计算价格函数,其中包含当前记录的条件。

我无法理解如何在 r 中执行此操作。

我尝试了以下

   train <- read.csv("Train.csv" )
   df <- as.data.frame.matrix(train)
   v = c(  df$Fuel.Type ,df$No.Gears)
   names(v ) <- c( "FuelType" ,"NoGears")
   df$FEType = FEType( v)

my 函数定义为

FEType <- function(v    ){
  ret="Low"
  if (v["FuelType"]=='G') {
    ret ="High"
  }
  return(ret)
}

这不符合我的预期 当我检查 v 时,我发现它包含总计而不是我预期的当前行。

我哪里错了?

here这个问题中,我在最后一段看到了一些提示。

为了重现问题,说明我想做什么,我有

IsPretty <-function(PetalWidth){
  if (PetalWidth  >0.3) return("Y")
  return("N")
}

df <- iris
df$Pretty = IsPretty(df$Petal.Width)
    

这给出了错误

条件的长度 > 1,并且只使用第一个元素

这让我开始研究向量。但我不相信这是正确的方向。

[更新]

我习惯于思考表格和当前记录。 所以我是这么想的

df$Pretty = IsPretty(df$Petal.Width)

将具有使用计算的 isPretty 属性向我的数据框添加列的效果

为什么我的计算中不能包含 if 条件?

【问题讨论】:

  • 另请注意,您得到的是警告,而不是错误。错误将停止执行您的代码。警告是 R 说某些东西似乎不规则,并要求您检查以确保它是正确的 - 但代码确实执行了。

标签: r if-statement vectorization


【解决方案1】:

向量化是您在 R 中需要习惯的最基本(也是最不寻常)的事情之一。许多(大多数?)R 操作都是向量化的。但有些东西不是 - 而if(){}else{} 是非矢量化的东西之一。它用于控制流(无论是否运行代码块)而不是矢量操作。 ifelse() 是用于向量的单独函数,其中第一个参数是“测试”,第二个和第三个参数是“如果是”和“如果不是”结果。测试是一个向量,返回值是测试中每个项目的相应是/否结果。 结果将与测试的长度相同

所以我们会这样写你的IsPretty函数:

IsPretty <- function(PetalWidth){
  return(ifelse(PetalWidth > 0.3, "Y", "N"))
}

df <- iris
df$Pretty = IsPretty(df$Petal.Width)

对比if(){...}else{...} 块,其中测试条件的长度为 1,并且可以在... 中运行任意代码 - 可能返回比测试更大的结果,或者更小的结果,或者没有结果 - 可能修改其他对象...你可以在if(){}else()中做任何事情,但测试条件的长度必须为1。

您可以一次使用您的IsPretty 函数一行 - 它适用于任何一行。所以我们可以把它放在下面的循环中,一次检查一行,一次给if()一个测试,一次分配一个结果。但是 R 针对向量化进行了优化,这会明显变慢并且是一个坏习惯。

IsPrettyIf <-function(PetalWidth){
  if (PetalWidth  >0.3) return("Y")
  return("N")
}

for(i in 1:nrow(df)) {
  df$PrettyLoop[i] = IsPrettyIf(df$Petal.Width[i])
}

下面的基准测试显示矢量化版本的速度提高了 50 倍。这是一个如此简单的案例和如此小的数据,这无关紧要,但在更大的数据上,或者通过更复杂的操作,矢量化代码和非矢量化代码之间的差异可能是几分钟而不是几天。

microbenchmark::microbenchmark(
  loop = {
    for(i in 1:nrow(df)) {
      df$PrettyLoop[i] = IsPrettyIf(df$Petal.Width[i])
    }
  },
  vectorized = {
    df$Pretty = IsPretty(df$Petal.Width)    
  }
)
Unit: microseconds
       expr    min     lq     mean median      uq     max neval
       loop 3898.9 4365.6 5880.623 5442.3 7041.10 11344.6   100
 vectorized   47.7   59.6  112.288   67.4   83.85  1819.4   100

这是 R 学习者的常见问题 - 您可以在 Stack Overflow 上找到许多问题,人们在需要 ifelse() 时使用 if(){}else{},反之亦然。 Why can't ifelse return vectors? 是来自问题另一面的常见问题解答。


你的尝试发生了什么?

df <- iris

## The condition has length equal to the number of rows in the data frame
df$Petal.Width > 0.3
#>   [1] FALSE FALSE FALSE FALSE FALSE  TRUE FALSE FALSE FALSE FALSE FALSE FALSE
#>  [13] FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE FALSE FALSE  TRUE FALSE  TRUE
## ... truncated


## R warns us that only the first value (which happens to be FALSE) is used
result = if(df$Petal.Width > 0.3) {"Y"} else {"N"}
#> Warning in if (df$Petal.Width > 0.3) {: the condition has length > 1 and only
#> the first element will be used

## So the result is a single "N"
result  
#> [1] "N"

length(result)
#> [1] 1


## R "recycles" inputs that are of insufficient length
## so we get a full column of "N"
df$Pretty = result
head(df)
#>   Sepal.Length Sepal.Width Petal.Length Petal.Width Species Pretty
#> 1          5.1         3.5          1.4         0.2  setosa      N
#> 2          4.9         3.0          1.4         0.2  setosa      N
#> 3          4.7         3.2          1.3         0.2  setosa      N
#> 4          4.6         3.1          1.5         0.2  setosa      N
#> 5          5.0         3.6          1.4         0.2  setosa      N
#> 6          5.4         3.9          1.7         0.4  setosa      N

reprex package (v0.3.0) 于 2020 年 11 月 8 日创建

【讨论】:

  • 我的问题的答案是我不能编写一个函数来对单个记录进行计算吗?我需要一次考虑所有记录吗?
  • 没有?您应该尝试编写一次处理n 记录的矢量化函数——其中n 是1 个或更多。在大多数情况下,这是可能的,而 R 使它相对容易和高效。
  • 可以编写对单个记录进行操作的函数——这正是您所做的。但是您随后需要在单个记录上调用此类函数 - 就像我在 for 循环示例中显示的那样。结果不正确的原因是您编写了一个对单个记录进行操作的函数,但随后您将所有记录作为参数传递 - IsPretty(df$Petal.Width) 正在对所有花瓣宽度记录调用 IsPretty
  • 您的问题中有一个library(dplyr) 呼叫,尽管您没有使用任何dplyr 函数。它是按组应用功能的优秀包 - 非常强大。
  • 有些函数不能向量化 - 如果ith 计算依赖于i - 1th 结果以非平凡的方式,你最终需要使用for一次循环并操作一条记录。但在大多数学科领域,这些任务相对较少且相差甚远。
【解决方案2】:

关于格雷戈尔的回答我自己的笔记

IsPrettyIf <-function(row){
 ret ="N"  
 if(row$Petal.Width > 0.3) { ret="Y"}
 return(ret)
}

 
df <- iris
df$PrettyLoop ="" # add a column and initialize all the cells to be empty
for(i in 1:5) {
  df$PrettyLoop[i] = IsPrettyIf(df[i,])
  cat("Row",i, "is Pretty?",df$PrettyLoop[i],"\n")
}

让我感到困惑的是 row$PrettyLoop 就像一个单元格,而 df$PrettyLoop 就像一个列,用电子表格类比思考。

【讨论】:

  • row$PrettyLoop 是一列 - 它只需要按照您的函数编写方式是单行数据框的一列,因此它也是一个单元格。如果你想使用这样的函数而不是向量化函数,你最好添加一个输入检查,比如if(nrow(row) &gt; 1) stop("Input should only have one row!")
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-07
相关资源
最近更新 更多