ti-enxame.com

Chame a função apply-like em cada linha de dataframe com vários argumentos de cada linha

Eu tenho um dataframe com várias colunas. Para cada linha no dataframe, desejo chamar uma função na linha e a entrada da função está usando várias colunas dessa linha. Por exemplo, digamos que eu tenha esses dados e este testFunc que aceita dois argumentos:

> 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

Digamos que eu queira aplicar este testFunc às colunas xez. Então, para a linha 1 eu quero 1 + 5, e para a linha 2 eu quero 2 + 6. Existe uma maneira de fazer isso sem escrever um loop for, talvez com a família de funções apply?

Eu tentei isso: 

> 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

Mas tem erro, alguma ideia?

EDIT: a função real que eu quero chamar não é uma soma simples, mas é power.t.test. Eu usei a + b apenas por exemplo. O objetivo final é ser capaz de fazer algo assim (escrito em pseudocódigo):

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

onde o resultado é um vetor de saídas para power.t.test para cada linha de df.

132
vasek1

Você pode aplicar apply a um subconjunto dos dados originais.

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

ou se sua função é apenas sum use a versão vetorizada:

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

Se você quiser usar testFunc

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

EDITPara acessar as colunas por nome e não indexar, você pode fazer algo assim:

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

Um data.frame é um list, então ...

Para funções vetorizadas do.call geralmente é uma boa aposta. Mas os nomes dos argumentos entram em jogo. Aqui sua testFunc é chamada com args x e y no lugar de a e b. O ... permite que argumentos irrelevantes sejam passados ​​sem causar um erro: 

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

Para as funções não-vetorizadas , mapply funcionará, mas você precisa corresponder à ordenação dos argumentos ou nomeá-los explicitamente:

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

Às vezes apply funcionará - como quando todos os args são do mesmo tipo, então coagir o data.frame para uma matriz não causa problemas alterando os tipos de dados. Seu exemplo foi desse tipo.

Se sua função deve ser chamada dentro de outra função na qual todos os argumentos são passados, existe um método muito mais claro que estes. Estude as primeiras linhas do corpo de lm() se quiser seguir esse caminho.

99
user2087984

Use 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
27
Chinmay Patil

Nova resposta com o pacote dplyr

Se a função que você deseja aplicar for vetorizada, , Você poderá usar a função mutate do pacote 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

Resposta antiga com pacote plyr

Na minha humilde opinião, A ferramenta mais adequada para a tarefa é mdply do pacote plyr.

Exemplo:

> 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

Infelizmente, como Bertjan Broeksema assinalou, Esta abordagem falha se você não usar todas as colunas do quadro de dados Na chamada mdply. Por exemplo ,

> 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)
16
I Like to Code

Outros têm apontado corretamente que mapply é feito para este propósito, mas (por uma questão de completude) um método conceitualmente mais simples é simplesmente usar um loop for

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

Muitas funções já são vetorização e, portanto, não há necessidade de quaisquer iterações (nem os loops for ou *pply). Seu testFunc é um desses exemplos. Você pode simplesmente ligar: 

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

Em geral, eu recomendaria tentar essas abordagens de vetorização primeiro e ver se elas conseguem os resultados pretendidos. 


Alternativamente, se você precisar passar vários argumentos para uma função que não é vetorizada, mapply pode ser o que você está procurando: 

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

Eu vim aqui procurando por tidyverse function name - que eu sabia que existia. Adicionando isto para (minha) referência futura e para entusiastas de tidyverse: purrrlyr:invoke_rows (purrr:invoke_rows em versões mais antigas). 

Com a conexão aos métodos de estatísticas padrão como na pergunta original, o pacote broom provavelmente ajudaria.

4
liborm

Aqui está uma abordagem alternativa. É mais intuitivo.

Um aspecto importante que sinto que algumas das respostas não levaram em conta, que eu indico para a posteridade, é apply () permite fazer cálculos de linha facilmente, mas apenas para dados matriciais (todos numéricos)

operações em colunas ainda são possíveis para dataframes:

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

Para operar em linhas, fazemos a transposição primeiro.

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

A desvantagem é que acredito que o R fará uma cópia da sua tabela de dados. O que poderia ser um problema de memória. (Isso é realmente triste, porque é programaticamente simples para tdf ser apenas um iterador para o df original, economizando memória, mas R não permite referenciamento de ponteiro ou iterador.)

Além disso, uma questão relacionada é como operar em cada célula individual em um dataframe. 

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

@ user20877984 da resposta é excelente. Como eles resumiram bem melhor do que minha resposta anterior, aqui está minha tentativa (possivelmente ainda de má qualidade) de uma aplicação do conceito:

Usando do.call de uma forma básica:

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

Trabalhando em um conjunto de dados completo:

# 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 a função power.t.test para cada uma das linhas de valores especificados:

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

data.table tem uma maneira muito intuitiva de fazer isso também:

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

O operador := pode ser chamado entre colchetes para adicionar uma nova coluna usando uma função

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

Também é fácil aceitar constantes como argumentos usando este método:

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
2
Pete M

Se as colunas data.frame forem de tipos diferentes, apply() tem um problema. Uma subtileza sobre a iteração de linha é como apply(a.data.frame, 1, ...) faz Conversão de tipo implícito para tipos de caracteres quando colunas são tipos diferentes; por exemplo. um fator e uma coluna numérica. Aqui está um exemplo, usando um fator Em uma coluna para modificar uma coluna numérica:

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

A subtração falha porque as colunas são convertidas em tipos de caracteres.

Uma correção é converter a segunda coluna em um número:

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

Mas as conversões podem ser evitadas mantendo as colunas separadas E usando mapply():

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

mapply() é necessário porque [[ ]] não aceita um argumento vetorial. Então a iteração da coluna Poderia ser feita antes da subtração passando um vetor para [], Por um código um pouco mais feio:

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

Uma função realmente boa para isso é adply de plyr, especialmente se você quiser acrescentar o resultado ao dataframe original. Esta função e sua prima ddply me salvaram muitas dores de cabeça e linhas de código!

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

Alternativamente, você pode chamar a função desejada.

df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))
0
Zach S.