각 행의 여러 인수를 사용하여 각 데이터 프레임 행에서 apply-like 함수 호출


168

여러 열이있는 데이터 프레임이 있습니다. 데이터 프레임의 각 행에 대해 행에서 함수를 호출하고 함수의 입력이 해당 행의 여러 열을 사용하고 있습니다. 예를 들어,이 데이터와 두 개의 인수를 허용하는 testFunc가 있다고 가정 해 봅시다.

> 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

이 testFunc를 x 및 z 열에 적용하고 싶다고 가정 해 봅시다. 따라서 행 1의 경우 1 + 5를 원하고 행 2의 경우 2 + 6을 원합니다. for 루프를 작성하지 않고 적용 함수 패밀리를 사용 하여이 작업을 수행 할 수 있습니까?

나는 이것을 시도했다 :

> 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

그러나 어떤 아이디어라도 오류가 있습니까?

편집 : 호출하려는 실제 함수는 단순한 합계가 아니지만 power.t.test입니다. 예를 들어 a + b를 사용했습니다. 최종 목표는 의사 코드로 작성된 다음과 같은 작업을 수행하는 것입니다.

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
))

여기서 결과는 df의 각 행에 대한 power.t.test에 대한 출력 벡터입니다.


방법 은 stackoverflow.com/a/24728107/946850 을 참조하십시오 dplyr.
krlmlr

답변:


137

apply원본 데이터의 하위 집합에 적용 할 수 있습니다 .

 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) )

또는 함수가 합계라면 벡터화 된 버전을 사용하십시오.

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

사용하고 싶다면 testFunc

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

편집하다 색인이 아닌 이름으로 열에 액세스하려면 다음과 같이 할 수 있습니다.

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

@agstudy에게 감사드립니다. 인덱스 대신 이름으로 인수를 지정하는 방법이 있는지 알고 있습니까? 따라서 testFunc의 경우 apply (dat [, c ( 'x', 'z')], 1, [pseudocode] testFunc (a = x, b = y))와 같은 것입니까? 그 이유는 내가이 방식으로 power.t.test를 호출하고 있기 때문에 델타, 파워, sig.level 매개 변수를 미리 지정된 위치가있는 배열에 붙이지 않고 이름으로 참조 할 수 있기를 원합니다. 더 강력하기 때문에 그러한 입장을 언급합니다. 어쨌든 대단히 감사합니다!
vasek1

이전 댓글에 대해 죄송합니다. 입력을 마치기 전에 Enter 키를 누르십시오 :) 삭제하고 정식 버전을 게시했습니다.
vasek1

21
apply큰 data.frames에서는 사용하지 않고 전체 객체를 복사하여 행렬로 변환합니다. data.frame 내에 다른 클래스 객체가있는 경우에도 문제가 발생합니다.
mnel

105

A data.framelist 이므로 ...

들어 벡터화 기능 do.call 일반적으로 좋은 내기이다. 그러나 논쟁의 이름이 등장합니다. 여기서 testFunca와 b 대신 args x와 y로 호출됩니다. 는 ...관련이없는 인수가 오류를 발생시키지 않고 통과 할 수 있습니다 :

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

들어 비 벡터화 기능 , mapply작동하지만 당신은 그 이름을 명시 적으로 인수의 순서를 일치하거나해야합니다 :

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

때로는 apply모든 인수의 유형이 동일한 경우와 같이 작동합니다.data.frame 행렬에 해도 데이터 유형을 변경해도 문제가 발생하지 않습니다. 당신의 예는 이런 종류였습니다.

인수가 모두 전달되는 다른 함수 내에서 함수를 호출해야하는 경우에는 그보다 훨씬 더 매끄러운 방법이 있습니다. lm()그 길을 가고 싶다면 몸의 첫 줄을 연구하십시오 .


8
내가 할 수 있다면 +10 SO에 오신 것을 환영합니다. 훌륭한 답변- 함수를 벡터화 Vectorize하기위한 래퍼로 언급 할 가치가 있습니다.mapply
mnel

와우, 그것은 매끈하다. 내가 사용한 원래 함수는 벡터화되지 않았지만 (power.t.test 위에 사용자 정의 확장), 벡터화하고 do.call (...)을 사용한다고 생각합니다. 감사!
vasek1

3
apply가 df를 행렬로 변환하기 때문에 apply (df, 1, function (row) ...)가 잘못 될 수 있음을 이미 언급했다는 점을 반복하십시오 !!!! 이것은 나쁠 수 있으며 머리카락을 많이 잡아 당깁니다. 적용 할 대안이 많이 필요합니다!
Colin D

Vectorized / non-vectorized를 차별화 해 주셔서 대단히 감사합니다. 이것은 제가 찾던 답입니다.
User632716

31

사용하다 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

dplyr패키지 와 함께 새로운 답변

적용하려는 함수가 벡터화 mutate된 경우 dplyr패키지 에서 해당 함수를 사용할 수 있습니다 .

> 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

plyr패키지 와 함께 옛 대답

겸손한 견해로는, 작업에 가장 적합한 도구 mdplyplyr패키지입니다.

예:

> 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

불행히도 Bertjan Broeksema가 지적했듯이 mdply호출 에서 데이터 프레임의 모든 열을 사용하지 않으면이 방법이 실패합니다 . 예를 들어

> 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
열 수가 적을 때 좋습니다. mdply (df, function (col1, col3) {})와 같은 작업을 시도했지만 col2가 사용되지 않는다고 불평하는 mdply가 사라졌습니다. 이제 수십 또는 수백 개의 열이있는 경우이 접근법은 그리 매력적이지 않습니다.
Bertjan Broeksema

1
@BertjanBroeksema는 많은 열을 수정하기 위해 사용할 수 있습니다 dplyr::mutate_each. 예를 들면 다음과 같습니다 iris %>% mutate_each(funs(half = . / 2),-Species)..
Paul Rougieux

당신은 기능에 수백 개나 수백을 넘겨서 사용할 수 없습니까? 그 오류를 해결해야합니까?
Shawn

11

다른 사람들은 mapply이 목적을 위해 만들어진 것을 올바르게 지적 했지만 (완전성을 위해) 개념적으로 더 간단한 방법은 for루프 를 사용하는 것 입니다.

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

1
네가 옳아. mapply를 효과적으로 사용하려면 특히 C ++ 또는 C #과 같은 절차 적 프로그래밍 배경에서 온 경우 장면 뒤의 "for"루프라는 것을 이해해야한다고 생각합니다.
Contango

10

많은 함수가 이미 벡터화되어 있으므로 반복 ( for루프 또는 *pply함수)이 필요하지 않습니다 . 당신 testFunc은 그러한 예 중 하나입니다. 간단하게 전화를 걸 수 있습니다.

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

일반적으로 이러한 벡터화 방법을 먼저 시도하여 원하는 결과를 얻을 수 있는지 확인하는 것이 좋습니다.


또는 벡터화되지 않은 함수에 여러 인수를 전달 해야하는 경우 mapply찾고있는 것일 수 있습니다.

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

이거 정말 달콤 하네. mapply에서 이름으로 인수를 지정하는 방법이 있는지 알고 있습니까? 즉 [의사 코드] mapply (power.t.test, delta = df [, 'delta'], power = df [, 'power'], ...) 같은 것?
vasek1

1
그래, 당신이 가진 그대로입니다! ;)
Ricardo Saporta

4

다른 접근법이 있습니다. 더 직관적입니다.

후손을 지적하면서 대답 중 일부가 고려하지 않았다고 생각하는 주요 측면은 apply ()를 사용하면 행 계산을 쉽게 수행 할 수 있지만 행렬 (모든 숫자) 데이터에 대해서만 가능하다는 것입니다

열에 대한 작업은 여전히 ​​데이터 프레임에 가능합니다.

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

행에서 작업하기 위해 먼저 조옮김을 만듭니다.

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

단점은 R이 데이터 테이블의 사본을 만들 것이라고 믿습니다. 메모리 문제 일 수 있습니다. tdf가 원래의 df에 대한 반복자 일뿐이므로 메모리를 절약하는 것이 프로그래밍 방식으로 간단하지만 R은 포인터 또는 반복자 참조를 허용하지 않기 때문에 이것은 매우 슬픈 일입니다.

또한 관련 질문은 데이터 프레임의 각 개별 셀에서 작동하는 방법입니다.

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

4

나는 깔끔한 함수 이름을 찾고 여기에 왔으며 , 나는 그것이 존재한다는 것을 알고 있었다. (나의) 나중의 참조 및 tidyverse열광자를 위해 이것을 추가 : purrrlyr:invoke_rows(purrr:invoke_rows 이전 버전).

원래 질문에서와 같이 표준 통계 방법과 관련하여 빗자루 패키지가 도움이 될 것입니다.


3

@ user20877984의 답변은 훌륭합니다. 그들이 이전의 대답보다 훨씬 잘 요약 했으므로 여기에 개념을 적용하려는 시도가 있습니다.

사용하는 do.call기본 방식 :

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

전체 데이터 세트 작업 :

# 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

lapplypower.t.test지정된 값의 각 행에 대한 함수 :

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
... ...

하하가 복잡했을까요? ;) 왜 t ()를 사용하고 over를 2적용하고 1있습니까?
Ricardo Saporta

3

data.table 이 작업을 수행하는 매우 직관적 인 방법이 있습니다.

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

:=연산자는 함수를 사용하여 새로운 컬럼을 추가 브래킷 내에서 호출 될 수있다

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

이 방법을 사용하여 상수를 인수로 쉽게 받아 들일 수도 있습니다.

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

data.frame 열이 다른 유형이면 apply()문제가 있습니다. 행 반복에 대한 미묘한 점은 apply(a.data.frame, 1, ...)열이 다른 유형일 때 암시 적 유형을 문자 유형으로 변환 하는 방법입니다 . 예. 요인과 숫자 열. 다음은 한 열의 요소를 사용하여 숫자 열을 수정하는 예입니다.

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]]])

열이 문자 유형으로 변환되므로 빼기가 실패합니다.

한 가지 수정은 두 번째 열을 숫자로 역변환하는 것입니다.

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

그러나 열을 별도로 유지하고 다음을 사용하면 변환을 피할 수 있습니다 mapply().

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

mapply()[[ ]]벡터 인수를 허용하지 않기 때문에 필요합니다 . 따라서 열을 반복하기 전에 []좀 더 못생긴 코드 로 벡터를 전달하여 빼기 전에 수행 할 수 있습니다 .

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

1

이것에 대한 정말 좋은 기능은 adplyfrom plyr, 특히 결과를 원본 데이터 프레임에 추가하려는 경우입니다. 이 기능과 그 사촌 덕분 ddply에 많은 두통과 코드가 절약되었습니다!

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

또는 원하는 기능을 호출 할 수 있습니다.

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

adply ()는 목록이나 데이터 프레임을 반환하는 함수를 처리 할 수 ​​있습니까? 예를 들어, testFunc ()가 목록을 반환하면 어떻게됩니까? unnest ()를 사용하여 df_appened의 추가 열로 변경합니까?
val
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.