Ich habe ein Datenfeld mit mehreren Spalten. Ich möchte für jede Zeile im Datenrahmen eine Funktion in der Zeile aufrufen, und die Eingabe der Funktion verwendet mehrere Spalten aus dieser Zeile. Angenommen, ich habe diese Daten und diese testFunc, die zwei Argumente akzeptiert:
> 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
Angenommen, ich möchte diese testFunc auf die Spalten x und z anwenden. Also für Zeile 1 möchte ich 1 + 5 und für Zeile 2 möchte ich 2 + 6.
Ich habe das versucht:
> 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
Aber haben Sie einen Fehler, irgendwelche Ideen?
EDIT: Die eigentliche Funktion, die ich aufrufen möchte, ist keine einfache Summe, sondern power.t.test. Ich habe a + b nur als Beispiel verwendet. Das Endziel ist, etwas in der Lage zu sein (in Pseudocode geschrieben):
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
))
wobei das Ergebnis ein Vektor von Ausgaben für power.t.test für jede Zeile von df ist.
Sie können apply
auf einen Teil der Originaldaten anwenden.
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) )
oder wenn Ihre Funktion nur Summe ist, verwenden Sie die vektorisierte Version:
rowSums(dat[,c('x','z')])
[1] 6 8
Wenn Sie testFunc
verwenden möchten
testFunc <- function(a, b) a + b
apply(dat[,c('x','z')], 1, function(x) testFunc(x[1],x[2]))
EDITUm auf Spalten nach Name und nicht auf Index zuzugreifen, können Sie Folgendes tun:
testFunc <- function(a, b) a + b
apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))
Ein data.frame
ist ein list
, also ...
Für vektorisierte Funktionen do.call
ist normalerweise eine gute Wahl. Aber die Namen der Argumente kommen ins Spiel. Hier wird Ihr testFunc
mit args x und y anstelle von a und b aufgerufen. Mit dem ...
können irrelevante Argumente übergeben werden, ohne einen Fehler zu verursachen:
do.call( function(x,z,...) testFunc(x,z), df )
Für nicht vektorisierte Funktionen funktioniert mapply
, aber Sie müssen der Reihenfolge der Argumente entsprechen oder sie explizit benennen:
mapply(testFunc, df$x, df$z)
Manchmal funktioniert apply
- wie wenn alle Argumente vom gleichen Typ sind, so dass das Erzwingen des data.frame
in eine Matrix keine Probleme verursacht, indem Datentypen geändert werden. Dein Beispiel war von dieser Art.
Wenn Ihre Funktion innerhalb einer anderen Funktion aufgerufen werden soll, in die alle Argumente übergeben werden, gibt es eine viel schlankere Methode als diese. Studieren Sie die ersten Zeilen des Körpers von lm()
, wenn Sie diesen Weg gehen möchten.
Verwenden Sie 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
Wenn die Funktion, die Sie anwenden möchten, vektorisiert ist, , Können Sie die Funktion mutate
aus dem Paket dplyr
verwenden:
> 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
PaketMeiner bescheidenen Meinung nach ist Das für diese Aufgabe am besten geeignete Werkzeug mdply
aus dem plyr
Paket.
Beispiel:
> 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
Wie Bertjan Broeksema hervorgehoben hat, schlägt diese Vorgehensweise leider fehl, wenn Sie nicht alle Spalten des Datenrahmens verwenden im Aufruf 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)
Andere haben richtig darauf hingewiesen, dass mapply
für diesen Zweck gemacht wird, aber (der Vollständigkeit halber) ist eine konzeptuell einfachere Methode die Verwendung einer for
-Schleife.
for (row in 1:nrow(df)) {
df$newvar[row] <- testFunc(df$x[row], df$z[row])
}
Viele Funktionen sind bereits vektorisiert, so dass keine Iterationen erforderlich sind (weder for
loops noch *pply
-Funktionen). Ihr testFunc
ist ein solches Beispiel. Sie können einfach anrufen:
testFunc(df[, "x"], df[, "z"])
Im Allgemeinen würde ich empfehlen, zunächst solche Vektorisierungsansätze zu testen und zu prüfen, ob sie die gewünschten Ergebnisse erzielen.
Wenn Sie mehrere Argumente an eine nicht vektorisierte Funktion übergeben müssen, ist mapply
möglicherweise das, wonach Sie suchen:
mapply(power.t.test, df[, "x"], df[, "z"])
Ich kam hierher auf der Suche nach Tidyverse Funktionsnamen - von denen ich wusste, dass es sie gibt. Dies für (meine) zukünftige Referenz und für tidyverse
-Enthusiasten hinzufügen: purrrlyr:invoke_rows
(purrr:invoke_rows
in älteren Versionen).
Bei Verbindung zu Standardstatistiken wie in der ursprünglichen Frage würde das Paket broom wahrscheinlich helfen.
Hier ist ein alternativer Ansatz. Es ist intuitiver.
Ein Schlüsselaspekt, den ich für einige der Antworten nicht berücksichtigt habe, den ich für die Nachwelt unterstreiche, ist "apply ()", mit dem Sie Zeilenberechnungen problemlos durchführen können, jedoch nur für Matrixdaten (alle numerischen)
operationen für Spalten sind noch für Datenrahmen möglich:
as.data.frame(lapply(df, myFunctionForColumn()))
Um an Zeilen zu arbeiten, machen wir zuerst die Transponierung.
tdf<-as.data.frame(t(df))
as.data.frame(lapply(tdf, myFunctionForRow()))
Der Nachteil ist, dass ich glaube, dass R eine Kopie Ihrer Datentabelle erstellen wird. Dies könnte ein Speicherproblem sein. (Dies ist wirklich traurig, da es programmatisch einfach ist, dass tdf nur ein Iterator für das ursprüngliche df ist, wodurch Speicher eingespart wird, R jedoch keine Zeiger- oder Iteratorreferenzierung zulässt.)
Eine verwandte Frage ist auch, wie mit jeder einzelnen Zelle in einem Datenrahmen gearbeitet wird.
newdf <- as.data.frame(lapply(df, function(x) {sapply(x, myFunctionForEachCell()}))
Die Antwort von @ user20877984 ist ausgezeichnet. Da sie es weitaus besser zusammenfassen als meine bisherige Antwort, ist hier mein (möglicherweise noch minderwertiger) Versuch einer Anwendung des Konzepts:
do.call
auf eine einfache Art und Weise verwenden:
powvalues <- list(power=0.9,delta=2)
do.call(power.t.test,powvalues)
An einem vollständigen Datensatz arbeiten:
# 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
die power.t.test
-Funktion für jede Zeile der angegebenen Werte:
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
hat auch eine sehr intuitive Art, dies zu tun:
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
Der :=
-Operator kann in Klammern aufgerufen werden, um mithilfe einer Funktion eine neue Spalte hinzuzufügen
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
Es ist auch einfach, Konstanten mit dieser Methode als Argumente zu akzeptieren:
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
Wenn data.frame-Spalten unterschiedliche Typen sind, hat apply()
ein Problem . Eine Untertitelung über die Zeilenwiederholung ist die Art und Weise, wie apply(a.data.frame, 1, ...)
Implizite Typkonvertierung in Zeichentypen durchführt, wenn Spalten unterschiedliche Typen sind; eine Faktor- und eine numerische Spalte. Hier ein Beispiel, bei dem ein Faktor In einer Spalte verwendet wird, um eine numerische Spalte zu ändern:
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]]])
Die Subtraktion schlägt fehl, da die Spalten in Zeichentypen konvertiert werden.
Ein Fix besteht darin, die zweite Spalte in eine Zahl umzuwandeln:
apply(subjects, 1, function(x) as.numeric(x[2]) - mean.height[[x[1]]])
Die Konvertierungen können jedoch vermieden werden, indem die Spalten voneinander getrennt werden Und mapply()
:
mapply(function(x,y) y - mean.height[[x]], subjects$gender, subjects$height)
mapply()
ist erforderlich, da [[ ]]
kein Vektorargument akzeptiert. Die Spalte Iteration könnte also vor der Subtraktion durchgeführt werden, indem ein Vektor an []
, Durch einen etwas hässlicheren Code übergeben wird:
subjects$height - unlist(mean.height[subjects$gender])
Eine wirklich schöne Funktion ist adply
von plyr
, vor allem wenn Sie das Ergebnis an den ursprünglichen Datenrahmen anhängen möchten. Diese Funktion und ihr Verwandter ddply
haben mir eine Menge Kopfschmerzen und Codezeilen erspart!
df_appended <- adply(df, 1, mutate, sum=x+z)
Alternativ können Sie die gewünschte Funktion aufrufen.
df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))