提问人:AWaddington 提问时间:5/20/2022 最后编辑:AWaddington 更新时间:5/21/2022 访问量:48
在 R 中有效地将大型 data.frames 传递给类似 Apply 的函数
Passing Large data.frames to Apply-like Functions Efficiently in R
问:
这是一个关于将大型数据集传递给类似 apply 的函数时的资源和效率的问题。
例
[编辑:更改了示例和描述,以说明多个表格的使用以及每个@UWE的评论的计算步骤]
library(dplyr)
#> Warning: package 'dplyr' was built under R version 4.0.5 ...[snip]...
set.seed(10)
# Objective: Add period-cost per portion of person's selected fruit
df.A <- data.frame(
period = rep(1:3, times = 1, each = 4),
Name = rep(c("John", "Paul","Ringo", "George"), times = 3),
Fruit = sample(c("Apple", "Pear", "Banana", "Apple"), size = 12, replace = TRUE)
) # extend to many people, many periods, many fruit
df.B <- data.frame(
Fruit = c("Pear", "Apple", "Banana"),
id = c(1, 2, 3),
pound.per.portion = c(0.396832,0.440925,0.299829)
) # one entry per fruit
df.C <- data.frame(
id = rep(1:3, times=3),
period = rep(1:3, times = 1, each = 3),
price.pound = c(2.33, 0.99, 2.15, 2.38, 1.01, 2.20, 2.42, 1.04, 2.25)
) # one entry per fruit per period
df.A
#> period Name Fruit
#> 1 1 John Banana
#> 2 1 Paul Apple
#> 3 1 Ringo Pear
#> 4 1 George Apple
#> 5 2 John Apple
#> 6 2 Paul Banana
#> 7 2 Ringo Apple
#> 8 2 George Pear
#> 9 3 John Banana
#> 10 3 Paul Banana
#> 11 3 Ringo Banana
#> 12 3 George Apple
df.B
#> Fruit id pound.per.portion
#> 1 Pear 1 0.396832
#> 2 Apple 2 0.440925
#> 3 Banana 3 0.299829
df.C
#> id period price.pound
#> 1 1 1 2.33
#> 2 2 1 0.99
#> 3 3 1 2.15
#> 4 1 2 2.38
#> 5 2 2 1.01
#> 6 3 2 2.20
#> 7 1 3 2.42
#> 8 2 3 1.04
#> 9 3 3 2.25
df.A$portion.price <- apply(df.A, MARGIN = 1,
FUN = function(x, legend, prices){
# please ignore efficiency of this function
# the internal function is not the focus of the question
fruit.info <- df.B[df.B$Fruit == x[["Fruit"]],]
cost <- df.C %>%
filter(period == x[["period"]],
id == fruit.info[["id"]]) %>%
select(price.pound) %>%
`*`(fruit.info$pound.per.portion)
cost[[1]]
},
legend = df.B, prices = df.C)
# Question relates to passing of legend and prices
# if `apply` passes df.B and df.C many times
# and df.B, df.C are large - is this inefficient, is there a better way
head(df.A, 5)
#> period Name Fruit portion.price
#> 1 1 John Banana 0.6446323
#> 2 1 Paul Apple 0.4365157
#> 3 1 Ringo Pear 0.9246186
#> 4 1 George Apple 0.4365157
#> 5 2 John Apple 0.4453343
创建于 2022-05-20 由 reprex 软件包 (v2.0.1)
此示例中的目标是向 df 添加一列。A 显示特定人在特定时期内选择的水果的部分成本。
存在三组数据,尽管它们都没有单独提供计算所需的所有信息。
df.A
包含他们选择的水果的人物、时期和名称。每个期间的每个人都有一个条目。
df.C
按时期提供水果的价格信息,但价格表示为每磅价格,而不是份价格,并且数据集无法识别水果名称(仅识别 ID 号)。每个时期的每个水果都有一个条目。
df.B
提供缺少的信息。首先,它定义了 ,并提供了一个将每磅成本转换为每份成本的因子。每个水果只需要一个条目。df.C$id
df.A$Name
对于每一行,将人的水果名称和周期以及两个引用集 ( 和 ) 传递给函数。该函数查找必要的信息,从中引用数据,然后使用这些信息来计算每部分的成本(返回)。df.A
apply
df.B
df.C
df.B
df.C
函数本身对于这个问题并不重要,只是说明使用多个数据集来查找每行的值。
这个例子很简单(四个人,三个时期,三个水果),并且非常易于管理;但是,从理论上讲,这些数据集中的每一个都可以包含数千行。apply
讨论主题从这里开始
如果我理解正确,传递值而不是引用。我相信这意味着上面示例中的函数为每一行创建一个 和 的新副本。假设这是正确的,这感觉效率不高,尤其是在数据集很大的情况下。r
apply
df.B
df.C
df.A
在使用大型数据集时,有没有比这种查找/处理更好的解决方案?apply
我知道函数可以使用引用而不是值。是否会构建一个仅使用引用的自定义函数,或者是否有标准的现成方法?rcpp
rccp
apply
答:
将函数与 data.frames 一起使用有一个主要缺点,因为在继续操作之前将 data.frame 强制转换为矩阵(参见 Patrick Burns 的 The R Inferno 的第 8.2.38 节)。apply()
apply()
由于矩阵的所有元素都需要具有相同的类型,因此 data.frame 的所有列都被强制为一种通用数据类型。
这可以通过以下方式进行验证
apply(df.A, MARGIN = 2, str)
chr [1:12] "1" "1" "1" "1" "2" "2" "2" "2" "3" "3" "3" "3" chr [1:12] "John" "Paul" "Ringo" "George" "John" "Paul" "Ringo" "George" "John" "Paul" "Ringo" "George" chr [1:12] "Banana" "Apple" "Pear" "Apple" "Apple" "Banana" "Apple" "Pear" "Banana" "Banana" "Banana" ...
在这里,整数列也被强制为键入字符。这很昂贵,并且可能会创建所有数据的副本。period
那么,我们能做些什么来实现OP的目标呢?
此示例中的目标是向 df 添加一列。A 显示 特定人在 特定时期。
恕我直言,实现目标的最佳方式是加入两次。
首先,创建一个查找表,其中包含 for each 和 。然后,使用更新联接将列追加到:lut
portion.price
Fruit
period
df.A
library(data.table)
lut <- setDT(df.B)[df.C, on = .(id)][, portion.price := pound.per.portion * price.pound][]
setDT(df.A)[lut, on = .(Fruit, period), portion.price := i.portion.price][]
period Name Fruit portion.price 1: 1 John Banana 0.6446323 2: 1 Paul Apple 0.4365157 3: 1 Ringo Pear 0.9246186 4: 1 George Apple 0.4365157 5: 2 John Apple 0.4453343 6: 2 Paul Banana 0.6596238 7: 2 Ringo Apple 0.4453343 8: 2 George Pear 0.9444602 9: 3 John Banana 0.6746152 10: 3 Paul Banana 0.6746152 11: 3 Ringo Banana 0.6746152 12: 3 George Apple 0.4585620
data.table
旨在有效地处理大型数据集。
或者,可以使用 SQL:
sqldf::sqldf("
select period, Name, Fruit, `portion.price` from `df.A`
left join (
select Fruit, period,
`pound.per.portion` * `price.pound` as `portion.price` from `df.B`
join `df.C` using(id)
) using(period, Fruit)
")
period Name Fruit portion.price 1 1 John Banana 0.6446323 2 1 Paul Apple 0.4365157 3 1 Ringo Pear 0.9246186 4 1 George Apple 0.4365157 5 2 John Apple 0.4453343 6 2 Paul Banana 0.6596238 7 2 Ringo Apple 0.4453343 8 2 George Pear 0.9444602 9 3 John Banana 0.6746152 10 3 Paul Banana 0.6746152 11 3 Ringo Banana 0.6746152 12 3 George Apple 0.4585620
请注意,某些表名和列名括在反引号中,因为句点在 SQL 中具有特殊含义
评论
setDT
评论
data.table
by = .EACHI
apply()