Gọi hàm giống như áp dụng trên mỗi hàng của khung dữ liệu với nhiều đối số từ mỗi hàng


168

Tôi có một khung dữ liệu với nhiều cột. Đối với mỗi hàng trong khung dữ liệu, tôi muốn gọi một hàm trên hàng và đầu vào của hàm đang sử dụng nhiều cột từ hàng đó. Ví dụ: giả sử tôi có dữ liệu này và testFunc này chấp nhận hai đối số:

> df <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6))
> df
  x y z
1 1 3 5
2 2 4 6
> testFunc <- function(a, b) a + b

Giả sử tôi muốn áp dụng testFunc này cho các cột x và z. Vì vậy, đối với hàng 1 tôi muốn 1 + 5 và cho hàng 2 tôi muốn 2 + 6. Có cách nào để làm điều này mà không cần viết một vòng lặp for, có thể với họ hàm áp dụng không?

Tôi đã thử điều này:

> df[,c('x','z')]
  x z
1 1 5
2 2 6
> lapply(df[,c('x','z')], testFunc)
Error in a + b : 'b' is missing

Nhưng có lỗi, có ý kiến ​​gì không?

EDIT: hàm thực tế tôi muốn gọi không phải là một tổng đơn giản, mà là power.t.test. Tôi đã sử dụng a + b chỉ cho mục đích ví dụ. Mục tiêu cuối cùng là có thể làm một cái gì đó như thế này (được viết bằng mã giả):

df = data.frame(
    delta=c(delta_values), 
    power=c(power_values), 
    sig.level=c(sig.level_values)
)

lapply(df, power.t.test(delta_from_each_row_of_df, 
                        power_from_each_row_of_df, 
                        sig.level_from_each_row_of_df
))

trong đó kết quả là một vectơ đầu ra cho power.t.test cho mỗi hàng của df.


Xem thêm stackoverflow.com/a/24728107/946850 để biết dplyrcách.
krlmlr

Câu trả lời:


137

Bạn có thể áp dụng applycho một tập hợp con của dữ liệu gốc.

 dat <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6))
 apply(dat[,c('x','z')], 1, function(x) sum(x) )

hoặc nếu chức năng của bạn chỉ là tổng sử dụng phiên bản véc tơ:

rowSums(dat[,c('x','z')])
[1] 6 8

Nếu bạn muốn sử dụng testFunc

 testFunc <- function(a, b) a + b
 apply(dat[,c('x','z')], 1, function(x) testFunc(x[1],x[2]))

EDIT Để truy cập các cột theo tên và không lập chỉ mục, bạn có thể làm một cái gì đó như thế này:

 testFunc <- function(a, b) a + b
 apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))

cảm ơn @agstudy, đã làm việc! Bạn có biết có cách nào để xác định các đối số theo tên thay vì theo chỉ mục không? vì vậy, đối với testFunc, một cái gì đó như áp dụng (dat [, c ('x', 'z')], 1, [pseudocode] testFunc (a = x, b = y))? Lý do là tôi đang gọi power.t.test theo cách này và tôi rất thích có thể tham chiếu các thông số delta, power, sig.level bằng tên thay vì dán chúng vào một mảng với các vị trí được chỉ định trước và sau đó tham khảo các vị trí đó, vì lý do mạnh mẽ hơn. trong mọi trường hợp cảm ơn rất nhiều
Vasek1

xin lỗi về nhận xét trước đó, nhấn enter trước khi gõ xong :) đã xóa nó và đăng phiên bản đầy đủ.
Vasek1

21
Không sử dụng applytrên data.frames lớn, nó sẽ sao chép toàn bộ đối tượng (để chuyển đổi thành ma trận). Điều này cũng sẽ gây ra vấn đề Nếu bạn có các đối tượng lớp khác nhau trong data.frame.
mnel

105

A data.framelà một list, vì vậy ...

Đối với các chức năng vector hóa do.call thường là một đặt cược tốt. Nhưng tên của các đối số đi vào chơi. Ở đây của bạn testFuncđược gọi với args x và y thay cho a và b. Việc ...cho phép các đối số không liên quan được thông qua mà không gây ra lỗi:

do.call( function(x,z,...) testFunc(x,z), df )

Đối với các hàm không được vector hóa , mapplysẽ hoạt động, nhưng bạn cần khớp với thứ tự của các đối số hoặc đặt tên rõ ràng cho chúng:

mapply(testFunc, df$x, df$z)

Đôi khi applysẽ hoạt động - vì khi tất cả các đối số cùng loại nên việc ép buộc data.framevào ma trận không gây ra vấn đề bằng cách thay đổi loại dữ liệu. Ví dụ của bạn là loại này.

Nếu hàm của bạn được gọi trong một hàm khác mà tất cả các đối số được truyền vào, thì có một phương thức lắt léo hơn các hàm này. Nghiên cứu các dòng đầu tiên của cơ thể lm()nếu bạn muốn đi theo con đường đó.


8
+10 nếu tôi có thể. Chào mừng đến với SO. câu trả lời tuyệt vời - có thể đáng nói đến Vectorizenhư là một trình bao bọc mapplyđể vector hóa các chức năng
mnel

wow, đó là khéo léo. Hàm ban đầu tôi đã sử dụng không được vector hóa (một phần mở rộng tùy chỉnh trên đầu trang power.t.test), nhưng tôi nghĩ rằng tôi sẽ vector hóa nó và sử dụng do.call (...). Cảm ơn!
Vasek1

3
Chỉ cần nhắc lại lưu ý rằng câu trả lời này đã nói rằng áp dụng (df, 1, hàm (hàng) ...) có thể xấu vì áp dụng chuyển đổi df thành ma trận !!!! Điều này có thể là xấu và dẫn đến nhiều kéo tóc. Các lựa chọn thay thế để áp dụng là rất cần thiết!
Colin D

Cảm ơn bạn rất nhiều vì sự khác biệt giữa Vectorized / không vector hóa, đây hoàn toàn là câu trả lời tôi đang tìm kiếm
User632716

31

Sử dụng mapply

> df <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6))
> df
  x y z
1 1 3 5
2 2 4 6
> mapply(function(x,y) x+y, df$x, df$z)
[1] 6 8

> cbind(df,f = mapply(function(x,y) x+y, df$x, df$z) )
  x y z f
1 1 3 5 6
2 2 4 6 8

20

Câu trả lời mới với dplyrgói

Nếu chức năng mà bạn muốn áp dụng được vector hóa, thì bạn có thể sử dụng mutatechức năng từ dplyrgói:

> library(dplyr)
> myf <- function(tens, ones) { 10 * tens + ones }
> x <- data.frame(hundreds = 7:9, tens = 1:3, ones = 4:6)
> mutate(x, value = myf(tens, ones))
  hundreds tens ones value
1        7    1    4    14
2        8    2    5    25
3        9    3    6    36

Câu trả lời cũ với plyrgói

Theo ý kiến ​​khiêm tốn của tôi, công cụ phù hợp nhất với nhiệm vụ là mdplytừ plyrgói.

Thí dụ:

> library(plyr)
> x <- data.frame(tens = 1:3, ones = 4:6)
> mdply(x, function(tens, ones) { 10 * tens + ones })
  tens ones V1
1    1    4 14
2    2    5 25
3    3    6 36

Thật không may, như Bertjan Broeksema đã chỉ ra, cách tiếp cận này thất bại nếu bạn không sử dụng tất cả các cột của khung dữ liệu trong mdplycuộc gọi. Ví dụ,

> library(plyr)
> x <- data.frame(hundreds = 7:9, tens = 1:3, ones = 4:6)
> mdply(x, function(tens, ones) { 10 * tens + ones })
Error in (function (tens, ones)  : unused argument (hundreds = 7)

1
Thật tuyệt khi bạn chỉ có một số lượng nhỏ các cột. Tôi đã cố gắng làm một cái gì đó như: mdply (df, function (col1, col3) {}) và mdply bails ra, phàn nàn col2 không được sử dụng. Bây giờ, nếu bạn có hàng chục hoặc thậm chí hàng trăm cột, cách tiếp cận này không hấp dẫn lắm.
Bertjan Broeksema 7/07/2015

1
@BertjanBroeksema để sửa đổi rất nhiều cột, bạn có thể sử dụng dplyr::mutate_each. Ví dụ : iris %>% mutate_each(funs(half = . / 2),-Species).
Paul Rougieux

Bạn không thể chuyển elipses, hoặc hàng trăm vào chức năng và chỉ không sử dụng nó? Điều đó có nên sửa lỗi đó không?
Shawn

11

Những người khác đã chỉ ra một cách chính xác mapplyđược thực hiện cho mục đích này, nhưng (vì mục đích hoàn chỉnh), một phương pháp đơn giản hơn về mặt khái niệm chỉ là sử dụng một forvòng lặp.

for (row in 1:nrow(df)) { 
    df$newvar[row] <- testFunc(df$x[row], df$z[row]) 
}

1
Bạn đúng. Để sử dụng mapply một cách hiệu quả, tôi nghĩ bạn phải hiểu rằng đó chỉ là một vòng lặp "cho" đằng sau hậu trường, đặc biệt nếu bạn đến từ một nền tảng lập trình thủ tục như C ++ hoặc C #.
Contango

10

Nhiều hàm đã được vector hóa, và do đó không cần bất kỳ phép lặp nào (không phải là forvòng lặp hoặc *pplyhàm). Bạn testFunclà một ví dụ như vậy. Bạn chỉ có thể gọi:

  testFunc(df[, "x"], df[, "z"])

Nói chung, tôi khuyên bạn nên thử các phương pháp vector hóa như vậy trước tiên và xem liệu chúng có mang lại cho bạn kết quả như mong muốn không.


Ngoài ra, nếu bạn cần truyền nhiều đối số cho một hàm không được vector hóa, mapply có thể là những gì bạn đang tìm kiếm:

  mapply(power.t.test, df[, "x"], df[, "z"])

ồ, ngọt ngào Bạn có biết có cách nào để xác định đối số theo tên trong mapply không? tức là một cái gì đó như [pseudocode] mapply (power.t.test, delta = df [, 'delta'], power = df [, 'power'], ...)?
Vasek1

1
Đúng, chính xác như bạn có nó! ;)
Ricardo Saporta

4

Đây là một cách tiếp cận thay thế. Nó trực quan hơn.

Một khía cạnh quan trọng tôi cảm thấy một số câu trả lời không được tính đến, mà tôi chỉ ra cho hậu thế, là áp dụng () cho phép bạn thực hiện các phép tính hàng dễ dàng, nhưng chỉ cho dữ liệu ma trận (tất cả số)

hoạt động trên các cột vẫn có thể cho dataframes:

as.data.frame(lapply(df, myFunctionForColumn()))

Để hoạt động trên các hàng, chúng tôi thực hiện chuyển đổi đầu tiên.

tdf<-as.data.frame(t(df))
as.data.frame(lapply(tdf, myFunctionForRow()))

Nhược điểm là tôi tin rằng R sẽ tạo một bản sao của bảng dữ liệu của bạn. Đó có thể là một vấn đề bộ nhớ. (Điều này thực sự đáng buồn, bởi vì tdf đơn giản về mặt lập trình để chỉ là một trình lặp cho df gốc, do đó tiết kiệm bộ nhớ, nhưng R không cho phép tham chiếu con trỏ hoặc trình lặp.)

Ngoài ra, một câu hỏi liên quan, là làm thế nào để hoạt động trên từng ô riêng lẻ trong một khung dữ liệu.

newdf <- as.data.frame(lapply(df, function(x) {sapply(x, myFunctionForEachCell()}))

4

Tôi đến đây để tìm tên hàm gọn gàng - mà tôi biết đã tồn tại. Thêm phần này cho (tôi) tài liệu tham khảo trong tương lai và cho tidyversenhững người đam mê: purrrlyr:invoke_rows(purrr:invoke_rows trong các phiên bản cũ hơn).

Với kết nối với các phương pháp thống kê tiêu chuẩn như trong câu hỏi ban đầu, gói chổi có thể sẽ giúp ích.


3

Câu trả lời của @ user20877984 là tuyệt vời. Vì họ tóm tắt nó tốt hơn nhiều so với câu trả lời trước đây của tôi, đây là nỗ lực của tôi (rõ ràng vẫn còn kém chất lượng) trong một ứng dụng của khái niệm:

Sử dụng do.callmột cách cơ bản:

powvalues <- list(power=0.9,delta=2)
do.call(power.t.test,powvalues)

Làm việc trên một tập dữ liệu đầy đủ:

# get the example data
df <- data.frame(delta=c(1,1,2,2), power=c(.90,.85,.75,.45))

#> df
#  delta power
#1     1  0.90
#2     1  0.85
#3     2  0.75
#4     2  0.45

lapplycác power.t.testchức năng để mỗi người trong số các hàng của các giá trị quy định:

result <- lapply(
  split(df,1:nrow(df)),
  function(x) do.call(power.t.test,x)
)

> str(result)
List of 4
 $ 1:List of 8
  ..$ n          : num 22
  ..$ delta      : num 1
  ..$ sd         : num 1
  ..$ sig.level  : num 0.05
  ..$ power      : num 0.9
  ..$ alternative: chr "two.sided"
  ..$ note       : chr "n is number in *each* group"
  ..$ method     : chr "Two-sample t test power calculation"
  ..- attr(*, "class")= chr "power.htest"
 $ 2:List of 8
  ..$ n          : num 19
  ..$ delta      : num 1
  ..$ sd         : num 1
  ..$ sig.level  : num 0.05
  ..$ power      : num 0.85
... ...

Haha có lẽ hỗn láo? ;) tại sao bạn sử dụng t () và áp dụng hơn 2, tại sao không chỉ áp dụng 1?
Ricardo Saporta

3

data.table có một cách thực sự trực quan để làm điều này là tốt:

library(data.table)

sample_fxn = function(x,y,z){
    return((x+y)*z)
}

df = data.table(A = 1:5,B=seq(2,10,2),C = 6:10)
> df
   A  B  C
1: 1  2  6
2: 2  4  7
3: 3  6  8
4: 4  8  9
5: 5 10 10

Các :=nhà điều hành có thể được gọi trong dấu ngoặc để thêm một cột mới bằng cách sử dụng chức năng

df[,new_column := sample_fxn(A,B,C)]
> df
   A  B  C new_column
1: 1  2  6         18
2: 2  4  7         42
3: 3  6  8         72
4: 4  8  9        108
5: 5 10 10        150

Cũng dễ dàng chấp nhận các hằng số làm đối số cũng bằng cách sử dụng phương thức này:

df[,new_column2 := sample_fxn(A,B,2)]

> df
   A  B  C new_column new_column2
1: 1  2  6         18           6
2: 2  4  7         42          12
3: 3  6  8         72          18
4: 4  8  9        108          24
5: 5 10 10        150          30

1

Nếu các cột data.frame là các loại khác nhau, apply()có vấn đề. Một sự tinh tế về phép lặp hàng là cách apply(a.data.frame, 1, ...)chuyển đổi kiểu ngầm định thành kiểu ký tự khi các cột là các kiểu khác nhau; ví dụ. một yếu tố và cột số. Dưới đây là một ví dụ, sử dụng một yếu tố trong một cột để sửa đổi cột số:

mean.height = list(BOY=69.5, GIRL=64.0)

subjects = data.frame(gender = factor(c("BOY", "GIRL", "GIRL", "BOY"))
         , height = c(71.0, 59.3, 62.1, 62.1))

apply(height, 1, function(x) x[2] - mean.height[[x[1]]])

Phép trừ không thành công vì các cột được chuyển đổi thành các loại ký tự.

Một cách khắc phục là chuyển đổi ngược cột thứ hai thành số:

apply(subjects, 1, function(x) as.numeric(x[2]) - mean.height[[x[1]]])

Nhưng các chuyển đổi có thể tránh được bằng cách tách các cột và sử dụng mapply():

mapply(function(x,y) y - mean.height[[x]], subjects$gender, subjects$height)

mapply()là cần thiết bởi vì [[ ]]không chấp nhận một đối số vector. Vì vậy, việc lặp lại cột có thể được thực hiện trước khi trừ bằng cách chuyển một vectơ tới [], bằng một mã xấu hơn một chút:

subjects$height - unlist(mean.height[subjects$gender])

1

Một chức năng thực sự tốt cho việc này là adplytừ plyr, đặc biệt nếu bạn muốn nối kết quả vào khung dữ liệu gốc. Chức năng này và anh em họ của nó ddplyđã giúp tôi tiết kiệm rất nhiều vấn đề đau đầu và dòng mã!

df_appended <- adply(df, 1, mutate, sum=x+z)

Ngoài ra, bạn có thể gọi chức năng bạn mong muốn.

df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))

adply () có thể xử lý các hàm trả về danh sách hoặc dataframes không? ví dụ: nếu testFunc () trả về danh sách thì sao? liệu unest () có được sử dụng để biến đổi nó thành các cột bổ sung của df_appened không?
val
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.