Panggil fungsi apply-like pada setiap baris dataframe dengan beberapa argumen dari setiap baris

168

Saya memiliki kerangka data dengan banyak kolom. Untuk setiap baris dalam kerangka data, saya ingin memanggil fungsi di baris, dan input fungsi menggunakan beberapa kolom dari baris itu. Sebagai contoh, katakanlah saya memiliki data ini dan testFunc ini yang menerima dua argumen:

> 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

Katakanlah saya ingin menerapkan testFunc ini ke kolom x dan z. Jadi, untuk baris 1 saya ingin 1 + 5, dan untuk baris 2 saya ingin 2 + 6. Apakah ada cara untuk melakukan ini tanpa menulis perulangan for, mungkin dengan fungsi yang berlaku keluarga?

Saya mencoba ini:

> 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

Tapi ada kesalahan, ada ide?

EDIT: fungsi sebenarnya yang ingin saya panggil bukanlah jumlah yang sederhana, tetapi itu adalah power.t.test. Saya menggunakan + b hanya untuk tujuan contoh. Tujuan akhirnya adalah untuk dapat melakukan sesuatu seperti ini (ditulis dalam 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
))

di mana hasilnya adalah vektor output untuk power.t.test untuk setiap baris df.

vasek1
sumber
Lihat juga stackoverflow.com/a/24728107/946850 untuk dplyrcaranya.
krlmlr

Jawaban:

137

Anda dapat menerapkan applyke subset dari data asli.

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

atau jika fungsi Anda hanya menggunakan versi vektor:

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

Jika ingin digunakan testFunc

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

EDIT Untuk mengakses kolom dengan nama dan bukan indeks, Anda dapat melakukan sesuatu seperti ini:

 testFunc <- function(a, b) a + b
 apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))
agstudy
sumber
terima kasih @agstudy, itu berhasil! Apakah Anda tahu jika ada cara untuk menentukan argumen dengan nama, bukan dengan indeks? jadi, untuk testFunc, sesuatu seperti apply (dat [, c ('x', 'z')], 1, [pseudocode] testFunc (a = x, b = y))? alasannya adalah bahwa saya memanggil power.t.test dengan cara ini, dan saya ingin bisa merujuk delta, power, sig.level params dengan nama alih-alih menempelkannya ke dalam array dengan posisi yang ditentukan sebelumnya dan kemudian referensi posisi itu, dengan alasan lebih kuat. dalam hal apapun terima kasih banyak!
vasek1
maaf tentang komentar sebelumnya, tekan enter sebelum selesai mengetik :) dihapus dan diposting versi lengkap.
vasek1
21
Jangan gunakan applypada data.frame besar itu akan menyalin seluruh objek (untuk mengkonversi ke matriks). Ini juga akan menyebabkan masalah Jika Anda memiliki objek kelas yang berbeda di dalam data.frame.
mnel
105

A data.frameadalah list, jadi ...

Untuk fungsi vektor do.call biasanya merupakan taruhan yang bagus. Namun nama-nama argumen ikut bermain. Di sini Anda testFuncdipanggil dengan args x dan y di tempat a dan b. The ...memungkinkan args tidak relevan untuk diteruskan tanpa menyebabkan kesalahan:

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

Untuk fungsi non-vektor , mapplyakan berfungsi, tetapi Anda harus mencocokkan urutan args atau secara eksplisit menamainya:

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

Kadang apply- kadang akan bekerja - seperti ketika semua argumen dari jenis yang sama sehingga memaksa data.frameke matriks tidak menyebabkan masalah dengan mengubah tipe data. Contoh Anda dari jenis ini.

Jika fungsi Anda dipanggil dalam fungsi lain di mana argumen dilewati, ada metode yang jauh lebih licin daripada ini. Pelajari baris pertama tubuh lm()jika Anda ingin pergi rute itu.

pengguna2087984
sumber
8
+10 jika aku bisa. Selamat datang di SO. jawaban yang bagus - mungkin layak disebut Vectorizesebagai pembungkus untuk mapplyfungsi vektor
mnel
wow, itu bagus sekali. Fungsi asli yang saya gunakan bukan vektor (ekstensi kustom di atas power.t.test), tapi saya pikir saya akan membuat vektor dan menggunakannya do.call (...). Terima kasih!
vasek1
3
Hanya mengulangi catatan bahwa jawaban ini sudah mengatakan bahwa berlaku (df, 1, fungsi (baris) ...) bisa buruk karena berlaku mengubah df menjadi matriks !!!! Ini bisa berakibat buruk dan mengakibatkan banyak rambut menarik. Alternatif untuk menerapkan sangat dibutuhkan!
Colin D
Terima kasih banyak untuk membedakan antara Vectorized / non-vectorized, ini benar-benar jawaban yang saya cari
User632716
31

Menggunakan 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
Chinmay Patil
sumber
20

Jawaban baru dengan dplyrpaket

Jika fungsi yang ingin Anda terapkan adalah vektor, maka Anda bisa menggunakan mutatefungsi dari dplyrpaket:

> 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

Jawaban lama dengan plyrpaket

Menurut pendapat saya, alat yang paling cocok untuk tugas ini adalah mdplydari plyrpaket.

Contoh:

> 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

Sayangnya, seperti yang ditunjukkan Bertjan Broeksema , pendekatan ini gagal jika Anda tidak menggunakan semua kolom bingkai data dalam mdplypanggilan. Sebagai contoh,

> 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)
Saya Suka Kode
sumber
1
Sangat menyenangkan ketika Anda hanya memiliki sejumlah kecil kolom. Saya mencoba melakukan sesuatu seperti: mdply (df, function (col1, col3) {}) dan bails mdply, mengeluh col2 tidak digunakan. Sekarang, jika Anda memiliki puluhan atau bahkan ratusan kolom, pendekatan ini tidak terlalu menarik.
Bertjan Broeksema
1
@BertjanBroeksema untuk memodifikasi banyak kolom, bisa Anda gunakan dplyr::mutate_each. Sebagai contoh: iris %>% mutate_each(funs(half = . / 2),-Species).
Paul Rougieux
Tidak bisakah Anda hanya melewati elips, atau ratusan ke dalam fungsi dan tidak menggunakannya? Itu harus memperbaiki kesalahan itu?
Shawn
11

Orang lain telah dengan benar menunjukkan bahwa mapplydibuat untuk tujuan ini, tetapi (demi kelengkapan) metode yang lebih sederhana secara konseptual hanya menggunakan forloop.

for (row in 1:nrow(df)) { 
    df$newvar[row] <- testFunc(df$x[row], df$z[row]) 
}
Ruten
sumber
1
Kamu benar. Untuk menggunakan mapply secara efektif, saya pikir Anda harus memahami bahwa itu hanya "untuk" loop di belakang layar, terutama jika Anda berasal dari latar belakang pemrograman prosedural seperti C ++ atau C #.
Contango
10

Banyak fungsi sudah vektorisasi, sehingga tidak perlu untuk iterasi (baik forloop atau *pplyfungsi). Anda testFuncadalah salah satu contohnya. Anda cukup menelepon:

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

Secara umum, saya akan merekomendasikan mencoba pendekatan vektorisasi seperti itu terlebih dahulu dan melihat apakah mereka memberi Anda hasil yang Anda inginkan.


Atau, jika Anda perlu memberikan beberapa argumen ke fungsi yang tidak di-vektor-kan, mapplymungkin yang Anda cari:

  mapply(power.t.test, df[, "x"], df[, "z"])
Ricardo Saporta
sumber
Oh manisnya. Apakah Anda tahu jika ada cara untuk menentukan argumen dengan nama di mapply? yaitu sesuatu seperti [pseudocode] mapply (power.t.test, delta = df [, 'delta'], power = df [, 'power'], ...)?
vasek1
1
Yap, persis seperti yang Anda miliki! ;)
Ricardo Saporta
4

Berikut ini adalah pendekatan alternatif. Ini lebih intuitif.

Satu aspek kunci yang saya rasa beberapa jawaban tidak diperhitungkan, yang saya tunjukkan untuk anak cucu, berlaku () memungkinkan Anda melakukan perhitungan baris dengan mudah, tetapi hanya untuk data matriks (semua angka)

operasi pada kolom masih dimungkinkan untuk kerangka data:

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

Untuk beroperasi pada baris, kita buat transposnya terlebih dahulu.

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

Kelemahannya adalah saya percaya R akan membuat salinan tabel data Anda. Yang bisa jadi masalah memori. (Ini benar-benar menyedihkan, karena secara program sederhana untuk tdf hanya menjadi sebuah iterator ke df asli, sehingga menghemat memori, tetapi R tidak mengizinkan pointer atau iterator referensi.)

Selain itu, pertanyaan terkait, adalah bagaimana cara beroperasi pada setiap sel individu dalam kerangka data.

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

Saya datang ke sini mencari nama fungsi rapi - yang saya tahu ada. Menambahkan ini untuk referensi masa depan (saya) dan untuk tidyversepenggemar: purrrlyr:invoke_rows( purrr:invoke_rowsdalam versi yang lebih lama).

Dengan koneksi ke metode statistik standar seperti pada pertanyaan awal, paket sapu mungkin akan membantu.

Libib
sumber
3

Jawaban @ user20877984 sangat bagus. Karena mereka menyimpulkannya jauh lebih baik daripada jawaban saya sebelumnya, berikut ini adalah upaya saya (yang mungkin masih jelek) pada penerapan konsep:

Menggunakan do.callsecara dasar:

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

Bekerja pada set data lengkap:

# 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

lapplyyang power.t.testfungsi untuk masing-masing baris dari nilai yang ditetapkan:

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
... ...
email
sumber
Haha berbelit-belit mungkin? ;) mengapa Anda menggunakan t () dan melamar 2, mengapa tidak melamar saja 1?
Ricardo Saporta
3

data.table memiliki cara yang sangat intuitif untuk melakukan ini juga:

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 dapat disebut dalam tanda kurung untuk menambahkan kolom baru menggunakan fungsi

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

Juga mudah untuk menerima konstanta sebagai argumen dengan menggunakan metode ini:

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
Pete M
sumber
1

Jika kolom data.frame adalah tipe yang berbeda, apply()memiliki masalah. Kehalusan tentang iterasi baris adalah bagaimana apply(a.data.frame, 1, ...)konversi tipe implisit ke tipe karakter ketika kolom adalah tipe yang berbeda; misalnya. kolom faktor dan angka. Berikut ini contohnya, menggunakan faktor dalam satu kolom untuk memodifikasi kolom angka:

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

Pengurangan gagal karena kolom dikonversi ke tipe karakter.

Salah satu perbaikannya adalah dengan kembali mengonversi kolom kedua ke nomor:

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

Namun konversi dapat dihindari dengan memisahkan dan menggunakan kolom mapply():

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

mapply()diperlukan karena [[ ]]tidak menerima argumen vektor. Jadi iterasi kolom dapat dilakukan sebelum pengurangan dengan melewatkan vektor [], dengan kode yang sedikit lebih jelek:

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

Fungsi yang sangat bagus untuk ini adalah adplydari plyr, terutama jika Anda ingin menambahkan hasilnya ke kerangka data asli. Fungsi ini dan sepupunya ddplytelah menyelamatkan saya dari banyak sakit kepala dan baris kode!

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

Atau, Anda dapat memanggil fungsi yang Anda inginkan.

df_appended <- adply(df, 1, mutate, sum=testFunc(x,z))
Zach S.
sumber
dapatkah adply () menangani fungsi yang mengembalikan daftar atau bingkai data? mis., bagaimana jika testFunc () mengembalikan daftar? akankah undest () digunakan untuk mengubahnya menjadi kolom tambahan df_appened Anda?
val