J'ai un dataframe avec plusieurs colonnes. Pour chaque ligne du cadre de données, je souhaite appeler une fonction sur la ligne et l'entrée de la fonction utilise plusieurs colonnes de cette ligne. Par exemple, supposons que j'ai ces données et ce testFunc qui accepte deux arguments:
> 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
Disons que je veux appliquer ce testFunc aux colonnes x et z. Ainsi, pour la ligne 1, je veux 1 + 5, et pour la ligne 2, 2 + 6. Y a-t-il un moyen de faire cela sans écrire une boucle for, peut-être avec la famille de fonctions apply?
J'ai essayé ceci:
> 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
Mais vous avez une erreur, des idées?
EDIT: la fonction que je souhaite appeler n’est pas une simple somme, mais c’est power.t.test. J'ai utilisé a + b juste à titre d'exemple. Le but final est de pouvoir faire quelque chose comme ceci (écrit en pseudocode):
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
))
où le résultat est un vecteur de sorties pour power.t.test pour chaque ligne de df.
Vous pouvez appliquer apply
à un sous-ensemble des données d'origine.
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 si votre fonction est juste somme utiliser la version vectorisée:
rowSums(dat[,c('x','z')])
[1] 6 8
Si vous voulez utiliser testFunc
testFunc <- function(a, b) a + b
apply(dat[,c('x','z')], 1, function(x) testFunc(x[1],x[2]))
EDITPour accéder aux colonnes par nom et non par index, vous pouvez faire quelque chose comme ceci:
testFunc <- function(a, b) a + b
apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))
Un data.frame
est une list
, donc ...
Pour fonctions vectorisées do.call
est généralement un bon choix. Mais les noms des arguments entrent en jeu. Ici, votre testFunc
est appelée avec les arguments x et y à la place de a et b. Le ...
permet de passer des arguments non pertinents sans provoquer d'erreur:
do.call( function(x,z,...) testFunc(x,z), df )
Pour les fonctions non vectorisées , mapply
fonctionnera, mais vous devez respecter l'ordre des arguments ou les nommer explicitement:
mapply(testFunc, df$x, df$z)
Parfois, apply
fonctionnera - comme lorsque tous les arguments sont du même type, le fait de forcer le data.frame
à une matrice ne pose pas de problème en modifiant les types de données. Votre exemple était de ce genre.
Si votre fonction doit être appelée dans une autre fonction dans laquelle les arguments sont tous passés, il existe une méthode beaucoup plus sournoise que celles-ci. Étudiez les premières lignes du corps de lm()
si vous voulez suivre cette voie.
Utilisez 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
Si la fonction que vous souhaitez appliquer est vectorisée, vous pouvez utiliser la fonction mutate
du package 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
À mon humble avis, l’outil le mieux adapté à cette tâche est mdply
du package plyr
.
Exemple:
> 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
Malheureusement, comme l'a souligné Bertjan Broeksema , Cette approche échoue si vous n'utilisez pas toutes les colonnes du bloc de données dans l'appel mdply
. Par exemple,
> 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)
D'autres ont correctement souligné que mapply
est créé à cette fin, mais (par souci d'exhaustivité) une méthode conceptuellement plus simple consiste simplement à utiliser une boucle for
.
for (row in 1:nrow(df)) {
df$newvar[row] <- testFunc(df$x[row], df$z[row])
}
De nombreuses fonctions étant déjà vectorisées, aucune itération n'est nécessaire (ni les boucles for
ni les fonctions *pply
). Votre testFunc
en est un exemple. Vous pouvez simplement appeler:
testFunc(df[, "x"], df[, "z"])
En général, je vous recommanderais d'essayer d'abord ces approches de vectorisation et de voir si elles vous donnent les résultats escomptés.
Sinon, si vous devez passer plusieurs arguments à une fonction non vectorisée, mapply
peut être ce que vous recherchez:
mapply(power.t.test, df[, "x"], df[, "z"])
Je suis venu ici à la recherche de tidyverse nom de la fonction - dont je savais qu’il existait. Ajout de ceci pour (ma) future référence et pour les passionnés de tidyverse
: purrrlyr:invoke_rows
(purrr:invoke_rows
dans les versions antérieures).
Avec la connexion aux méthodes de statistiques standard comme dans la question initiale, le paquet broom serait probablement utile.
Voici une approche alternative. C'est plus intuitif.
Un aspect clé que certaines des réponses ne prennent pas en compte et que je signale pour la postérité est que apply () vous permet de faire des calculs de lignes facilement, mais uniquement pour les données matricielles (toutes numériques).
les opérations sur les colonnes sont encore possibles pour les cadres de données:
as.data.frame(lapply(df, myFunctionForColumn()))
Pour opérer sur les lignes, nous faisons d’abord la transposition.
tdf<-as.data.frame(t(df))
as.data.frame(lapply(tdf, myFunctionForRow()))
L'inconvénient est que je pense que R va faire une copie de votre tableau de données ..___, ce qui pourrait être un problème de mémoire. (C’est vraiment triste, car tdf est un programme simple, il suffit d’être un itérateur du fichier original, économisant ainsi de la mémoire, mais R ne permet pas le référencement de pointeur ou d’itérateur.)
En outre, une question connexe est de savoir comment agir sur chaque cellule individuelle dans une trame de données.
newdf <- as.data.frame(lapply(df, function(x) {sapply(x, myFunctionForEachCell()}))
La réponse de @ user20877984 est excellente. Puisqu'ils résument bien mieux que ma réponse précédente, voici ma tentative (peut-être encore médiocre) d'application du concept:
Utiliser do.call
de manière basique:
powvalues <- list(power=0.9,delta=2)
do.call(power.t.test,powvalues)
Travailler sur un ensemble de données complet:
# 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
la fonction power.t.test
à chacune des lignes des valeurs spécifiées:
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
a également une manière très intuitive de le faire:
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
L'opérateur :=
peut être appelé entre crochets pour ajouter une nouvelle colonne à l'aide d'une fonction
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
Il est également facile d'accepter des constantes comme arguments en utilisant cette méthode:
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
Si les colonnes data.frame sont de types différents, apply()
a un problème . Une subtilité à propos de l'itération de ligne correspond à la manière dont apply(a.data.frame, 1, ...)
effectue La conversion de type implicite en types de caractère lorsque les colonnes sont de types différents; une colonne de facteur et numérique. Voici un exemple, en utilisant un facteur Dans une colonne pour modifier une colonne numérique:
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]]])
La soustraction échoue car les colonnes sont converties en types de caractères.
Un correctif consiste à convertir en retour la deuxième colonne en un nombre:
apply(subjects, 1, function(x) as.numeric(x[2]) - mean.height[[x[1]]])
Mais les conversions peuvent être évitées en gardant les colonnes séparées Et en utilisant mapply()
:
mapply(function(x,y) y - mean.height[[x]], subjects$gender, subjects$height)
mapply()
est nécessaire car [[ ]]
n'accepte pas un argument de vecteur. Ainsi, l'itération de la colonne Pourrait être effectuée avant la soustraction en passant un vecteur à []
, Avec un code un peu plus laid:
subjects$height - unlist(mean.height[subjects$gender])
Une fonction vraiment intéressante pour cela est adply
à partir de plyr
, surtout si vous souhaitez ajouter le résultat au cadre de données d'origine. Cette fonction et sa cousine ddply
m'ont évité beaucoup de maux de tête et de lignes de code!
df_appended <- adply(df, 1, mutate, sum=x+z)
Alternativement, vous pouvez appeler la fonction que vous désirez.
df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))