我经常需要从数据框中提取单个值,例如提取大猩猩大猩猩的“ 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]
。
答案 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