根据逻辑条件,Tidyverse样式从数据框中提取单个值

时间:2020-08-25 12:47:10

标签: r dplyr tidyverse

我经常需要从数据框中提取单个值,例如提取大猩猩大猩猩的“ var1”值:

example_data <- data.frame(species = c("Pan_troglodytes", "Gorilla_gorilla", "Pongo_pygmaeus"),
                           var1 = c(5.88, 6.07, 5.83),
                           var2 = c(10.6, 11.2, 10.5)
                           )
example_data

          species var1 var2
1 Pan_troglodytes 5.88 10.6
2 Gorilla_gorilla 6.07 11.2
3  Pongo_pygmaeus 5.83 10.5

我用base R学习的方法是:

example_data[[which(example_data$species == "Gorilla_gorilla"), "var1"]]

[1] 6.07

此方法在概念上感觉非常有效(标识值的位置->提取值),并且在计算上肯定有效(请参见下面的基准测试)。但是,从编码的角度看,它似乎不是很有效:必须重复数据变量的名称,这可能导致视觉上混乱的代码,尤其是在数据变量的名称很长或使用方法的情况下。理想情况下,我更喜欢使用tidyverse样式的方法,该方法只需要声明一次数据变量即可。我知道dplyr::filter()可以实现相同的目的:

library(dplyr)

filter(example_data, species == "Gorilla_gorilla")$var1

[1] 6.07

这段代码更加简洁,但是从概念上讲效率低下(对整个数据框执行操作->提取值),并且计算速度较慢(尽管我们说的是微秒,所以这可能并不是问题)

library(microbenchmark)

Unit: microseconds
                                                                          expr     min       lq      mean   median       uq      max neval
 example_data[[which(example_data$species == "Gorilla_gorilla"),      "var1"]]   6.301   8.8505  15.48891  15.9505  19.1510   77.701   100
                       filter(example_data, species == "Gorilla_gorilla")$var1 638.801 801.8005 904.89208 859.3505 936.8505 1782.901   100

我的问题是:是否存在另一种从数据框中有条件地提取单个值的方法,该方法具有tidyverse简洁代码的优点,但更像基本R方法那样直接?还是我只是傻了,没有理由不只使用dplyr::filter()方法?

(此外,我知道我可以将“ species”变量设置为行名,然后使用example_data["Gorilla_gorilla", "var1"],这既干净又高效,但这违反了不使用行名的整洁原则)。 / p>

似乎整联中有一个空白:dplyr::filter等于基数data[n, ]dplyr::select等同于基本data[, n];但似乎没有等效的基数data[n, n]

4 个答案:

答案 0 :(得分:4)

在R中,有很多方法可以给猫做皮;这里有很多东西(请记住,您可以不断混合和匹配下面使用的许多功能),通常您已经正确地观察到了tidyverse函数的效率低下:

# Base R method 1: => returns data.frame:
subset(example_data, species == "Gorilla_gorilla", select = "var1")

# Base R method 2: => returns vector length 1 (R's scalar): 
example_data$var1[match("Gorilla_gorilla", example_data$species)]

# Base R method 3: => (result as df): 
example_data[match("Gorilla_gorilla", example_data$species), "var1", drop = FALSE]

# Tidyverse method 1: => returns df
library(tidyverse)
example_data %>% 
  slice(which(species == "Gorilla_gorilla")) %>% 
  select(var1)

# Tidyverse method 2: => returns df
example_data %>% 
  filter(species == "Gorilla_gorilla") %>% 
  select(var1)

# Tidyverse method 3: => returns vector
example_data %>% 
  filter(species == "Gorilla_gorilla") %>% 
  pull(var1)

# data.table method 1: => returns data.table / data.frame object
library(data.table)
setDT(example_data)[species == "Gorilla_gorilla", "var1"]

# data.tabel method 2: => returns data.table / data.frame object: 
setDT(example_data)[species == "Gorilla_gorilla", 2]

# data.tabel method 3: => returns data.table / data.frame object: 
setDT(example_data)[species == "Gorilla_gorilla", .SD, .SDcols = 2]

# data.table method 4: => return vector: 
as.matrix(setDT(example_data)[species == "Gorilla_gorilla", 2])[1]

答案 1 :(得分:3)

您熟悉data.table吗?

它符合简洁,(真正)高效且类似于基本R语法的三个条件:

library(data.table)
setDT(example_data) # Convert to data.table, convert back with setDF()
example_data[species == "Gorilla_gorilla", var1]
# [1] 6.07

如果您重复执行此操作,则可以设置一个键,以避免键入过滤器变量的名称:

setkey(example_data, species)

example_data["Gorilla_gorilla", var1]
# [1] 6.07
example_data["Pongo_pygmaeus", var1]
# [1] 5.83

答案 2 :(得分:1)

完整的tidyverse方法是使用pull仅获取变量而不是完整的data.frame:

example_data %>% 
  filter(species == "Gorilla_gorilla") %>% 
  pull(var1)
[1] 6.07

但是,我尚未对其进行计算效率测试。

答案 3 :(得分:1)

感谢@hello_friend,这对人们来说是一个非常有用的调查和参考。为了进一步参考,这是不同方法的基准测试(这就是为什么我必须将其发布为答案而不是评论)的原因。再说一次,我们所说的是微秒,因此,尽管我认为它们很有趣,但差异实际上并没有什么意义。

我认为我实际上会坚持使用原来的filter(example_data, species == "Gorilla_gorilla")$var1。现在已经能够将其与所有其他选项进行比较,看起来似乎还算不错,而且比我选择的第二个选择subset(example_data, species == "Gorilla_gorilla", select = "var1")[[1]]简明扼要,对于我来说,第二个选择要重要得多慢了几微秒。

通过@sindri_baldur,感谢有关data.table的提示,但我没有遇到。有趣的是,尽管预先完成了数据帧的转换(DT_data[species == "Gorilla_gorilla", "var1"]),但对于此特定操作而言,它实际上并没有更快的速度

   expr                                                                                          mean_time_microseconds package     
 1 "example_data$var1[match(\"Gorilla_gorilla\", example_data$species)]"                                         15413. base      
 2 "example_data[match(\"Gorilla_gorilla\", example_data$species),      \"var1\", drop = FALSE]"                561586. base      
 3 "subset(example_data, species == \"Gorilla_gorilla\", select = \"var1\")"                                    579747. base      
 4 "subset(example_data, species == \"Gorilla_gorilla\", select = \"var1\")[[1]]"                               604293. base      
 5 "filter(example_data, species == \"Gorilla_gorilla\")$var1"                                                 1757617. dplyr     
 6 "example_data %>% filter(species == \"Gorilla_gorilla\") %>% pull(var1)"                                    2699485. dplyr     
 7 "DT_data[species == \"Gorilla_gorilla\", \"var1\"]"                                                         2877631. data.table
 8 "setDT(example_data)[species == \"Gorilla_gorilla\", \"var1\"]"                                             3044128. data.table
 9 "setDT(example_data)[species == \"Gorilla_gorilla\", 2]"                                                    3094515. data.table
10 "as.matrix(setDT(example_data)[species == \"Gorilla_gorilla\", 2])[1]"                                      3168429. data.table
11 "setDT(example_data)[species == \"Gorilla_gorilla\", .SD, .SDcols = 2]"                                     3533138. data.table
12 "example_data %>% slice(which(species == \"Gorilla_gorilla\")) %>%      select(var1)"                       5835452. dplyr     
13 "example_data %>% filter(species == \"Gorilla_gorilla\") %>% select(var1)"                                  5882807. dplyr

enter image description here