IT박스

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

itboxs 2020. 6. 17. 19:20
반응형

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


여러 열이있는 데이터 프레임이 있습니다. 데이터 프레임의 각 행에 대해 행에서 함수를 호출하고 싶습니다. 함수 입력은 해당 행의 여러 열을 사용하고 있습니다. 예를 들어,이 데이터와 두 개의 인수를 허용하는 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에 대한 출력 벡터입니다.


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

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()그 길을 가고 싶다면 몸의 첫 줄을 연구하십시오 .


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

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

Unfortunately, as Bertjan Broeksema pointed out, this approach fails if you don't use all the columns of the data frame in the mdply call. For example,

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

Many functions are vectorization already, and so there is no need for any iterations (neither for loops or *pply functions). Your testFunc is one such example. You can simply call:

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

In general, I would recommend trying such vectorization approaches first and see if they get you your intended results.


Alternatively, if you need to pass multiple arguments to a function which is not vectorized, mapply might be what you are looking for:

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

Others have correctly pointed out that mapply is made for this purpose, but (for the sake of completeness) a conceptually simpler method is just to use a for loop.

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

Here is an alternate approach. It is more intuitive.

One key aspect I feel some of the answers did not take into account, which I point out for posterity, is apply() lets you do row calculations easily, but only for matrix (all numeric) data

operations on columns are possible still for dataframes:

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

To operate on rows, we make the transpose first.

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

The downside is that I believe R will make a copy of your data table. Which could be a memory issue. (This is truly sad, because it is programmatically simple for tdf to just be an iterator to the original df, thus saving memory, but R does not allow pointer or iterator referencing.)

Also, a related question, is how to operate on each individual cell in a dataframe.

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

I came here looking for tidyverse function name - which I knew existed. Adding this for (my) future reference and for tidyverse enthusiasts: purrrlyr:invoke_rows (purrr:invoke_rows in older versions).

With connection to standard stats methods as in the original question, the broom package would probably help.


@user20877984's answer is excellent. Since they summed it up far better than my previous answer, here is my (posibly still shoddy) attempt at an application of the concept:

Using do.call in a basic fashion:

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

Working on a full data set:

# 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

lapply the power.t.test function to each of the rows of specified values:

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

data.table has a really intuitive way of doing this as well:

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

The := operator can be called within brackets to add a new column using a function

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

It's also easy to accept constants as arguments as well using this method:

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

If data.frame columns are different types, apply() has a problem. A subtlety about row iteration is how apply(a.data.frame, 1, ...) does implicit type conversion to character types when columns are different types; eg. a factor and numeric column. Here's an example, using a factor in one column to modify a numeric column:

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

The subtraction fails because the columns are converted to character types.

One fix is to back-convert the second column to a number:

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

But the conversions can be avoided by keeping the columns separate and using mapply():

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

mapply() is needed because [[ ]] does not accept a vector argument. So the column iteration could be done before the subtraction by passing a vector to [], by a bit more ugly code:

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

A really nice function for this is adply from plyr, especially if you want to append the result to the original dataframe. This function and its cousin ddply have saved me a lot of headaches and lines of code!

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

Alternatively, you can call the function you desire.

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

참고URL : https://stackoverflow.com/questions/15059076/call-apply-like-function-on-each-row-of-dataframe-with-multiple-arguments-from-e

반응형