wake-up-neo.com

Rufen Sie die Anwendung-ähnliche Funktion für jede Zeile des Datenrahmens mit mehreren Argumenten aus jeder Zeile auf

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.

132
vasek1

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

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.

99
user2087984

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

Neue Antwort mit dem Paket 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

Alte Antwort mit plyr Paket

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

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]) 
}
10
rsoren

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"])
10
Ricardo Saporta

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.

4
liborm

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()}))
4
BAMF4bacon

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

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

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])
0
John Mark

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))
0
Zach S.