Викликайте функцію, подібну застосуванню, у кожному рядку фрейму даних з кількома аргументами з кожного рядка


168

У мене є кадр даних з кількома стовпцями. Для кожного рядка в кадрі даних я хочу викликати функцію в рядку, а для введення функції використовується декілька стовпців із цього рядка. Наприклад, скажімо, у мене є ці дані і цей testFunc, який приймає два аргументи:

> 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

Скажімо, я хочу застосувати цей testFunc до стовпців x і z. Отже, для рядка 1 я хочу 1 + 5, а для рядка 2 - 2 + 6. Чи є спосіб це зробити без написання циклу для циклу, можливо, із сімейством функцій застосувати?

Я спробував це:

> 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

Але помилилися, якісь ідеї?

EDIT: фактична функція, яку я хочу викликати, - це не проста сума, але це power.t.test. Я використовував + b лише для прикладних цілей. Кінцева мета - вміти робити щось подібне (написане псевдокодом):

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

де результат - вектор виходів для power.t.test для кожного ряду df.


Дивіться також stackoverflow.com/a/24728107/946850 про dplyrспосіб.
krlmlr

Відповіді:


137

Ви можете застосувати applyдо підмножини вихідних даних.

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

або якщо ваша функція є лише сумою, використовуйте векторизовану версію:

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

Якщо ви хочете використовувати testFunc

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

EDIT Для доступу до стовпців за назвою, а не за індексом, ви можете зробити щось подібне:

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

спасибі @agstudy, що спрацювало! чи знаєте ви, чи є спосіб вказати аргументи за назвою, а не за індексом? так, для testFunc щось подібне застосувати (dat [, c ('x', 'z')], 1, [псевдокод] testFunc (a = x, b = y))? причина полягає в тому, що я називаю power.t.test таким чином, і я хотів би мати можливість посилати параметри delta, power, sig.level за назвою, а не вставляти їх у масив із заздалегідь заданими позиціями, а потім посилаючись на цю позицію, з причини її більш міцної. у будь-якому випадку спасибі велике!
vasek1

вибачте за попередній коментар, натисніть Enter, перш ніж закінчити вводити :) видалив його та опублікував повну версію.
vasek1

21
Не використовуйте applyу великих data.frames, він буде копіювати весь об'єкт (для перетворення в матрицю). Це також спричинить проблеми, якщо у вас є об'єкти різних класів у межах data.frame.
mnel

105

А data.frameє list, так ...

Для векторизованих функцій do.call зазвичай хороша ставка. Але назви аргументів вступають у гру. Тут ваш testFuncназивається аргументами x і y замість a і b. ...Дозволяє недоречна арг бути передана без виникнення помилки:

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

Для НЕ-векторних функцій , mapplyбуде працювати, але ви повинні відповідати упорядкування Арга або явно назвати їх:

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

Іноді applyбуде працювати - як коли всі аргументи одного типу, тому примушування data.frameдо матриці не спричиняє проблем, змінюючи типи даних. Ваш приклад був подібного роду.

Якщо ваша функція повинна бути викликана в межах іншої функції, в яку передаються всі аргументи, існує набагато менший метод, ніж цей. Вивчіть перші лінії тіла, lm()якщо хочете пройти цей маршрут.


8
+10, якби міг. Ласкаво просимо до SO. чудова відповідь - це, можливо, варто згадати Vectorizeяк обгортку mapplyдля векторизації функцій
квітня 1313

вау, це гладко. Оригінальна функція, яку я використав, не була векторизованою (спеціальне розширення на вершині power.t.test), але я думаю, я її векторизую і буду використовувати do.call (...). Дякую!
vasek1

3
Просто повторюючи зауваження, що ця відповідь вже говорить про те, що застосовувати (df, 1, функція (рядок) ...) може бути погано, оскільки застосувати перетворює df в матрицю !!!! Це може бути погано і призведе до великої кількості волосся. Альтернативи для застосування дуже потрібні!
Колін Д

Дякую вам за розмежування векторизованого / невекторизованого, це абсолютно відповідь, яку я шукав
User632716

31

Використовуйте 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

20

Нова відповідь з dplyrпакетом

Якщо функція, яку ви хочете застосувати, векторизована, ви можете використовувати mutateфункцію з 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пакетом

На мою скромну думку, інструмент, який найкраще підходить для виконання завдання, - mdplyце plyrпакет.

Приклад:

> 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

На жаль, як зазначив Бертджан Броксема , такий підхід не вдається, якщо ви не використовуєте всі стовпці кадру даних у 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)

1
Приємно, коли у вас є лише невелика кількість стовпців. Я намагався зробити щось на кшталт: mdply (df, function (col1, col3) {}) і mdply видає заставу, скаржившись, що col2 не використовується. Тепер, якщо у вас десятки чи навіть сотні колонок, такий підхід не дуже привабливий.
Bertjan Broeksema

1
@BertjanBroeksema для зміни багатьох стовпців можна використовувати dplyr::mutate_each. Наприклад: iris %>% mutate_each(funs(half = . / 2),-Species).
Пол Рудьо

Не могли ви просто передати епізоди або сотні у функцію, а просто не використати їх? Це повинно виправити цю помилку?
Шон

11

Інші правильно вказали, що mapplyзроблено для цієї мети, але (заради повноти) концептуально простішим методом є просто використання forциклу.

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

1
Ти маєш рацію. Щоб ефективно використовувати карту, я думаю, ви повинні розуміти, що це просто цикл "для" за кадром, особливо якщо ви поступаєте з фонового процесу програмування, такого як C ++ або C #.
Контанго

10

Багато функцій вже є векторизацією, тому немає необхідності в будь-яких ітераціях (ні forциклів, ні *pplyфункцій). Ваш testFuncодин із таких прикладів. Ви можете просто зателефонувати:

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

Загалом, я б рекомендував спершу спробувати такі векторизаційні підходи і подивитися, чи отримають вони ваші намічені результати.


Крім того, якщо вам потрібно передати кілька аргументів функції, яка не векторизована, mapplyви можете шукати:

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

о, мила. Чи знаєте ви, чи є спосіб вказати аргументи по імені в mapply? тобто щось на зразок [pseudocode] mapply (power.t.test, delta = df [, 'delta'], power = df [, 'power'], ...)?
vasek1

1
Так, саме так, як у вас є! ;)
Рікардо Сапорта,

4

Ось альтернативний підхід. Це більш інтуїтивно зрозуміло.

Одним із ключових аспектів, на який я вважаю, що відповіді не враховано, на які я вказую для нащадків, є application () дозволяє легко робити обчислення рядків, але тільки для матричних (усіх числових) даних

операції над стовпцями можливі ще для фреймів даних:

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

Щоб оперувати рядками, спочатку робимо транспозицію.

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

Мінусом є те, що я вірю, що R зробить копію таблиці даних. Що може бути проблемою пам'яті. (Це справді сумно, тому що програміст tdf є просто ітератором оригінального df, тим самим економлячи пам'ять, але R не дозволяє посиланням на покажчик чи ітератор.)

Крім того, пов'язане питання - як оперувати кожною окремою коміркою у кадрі даних.

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

4

Я прийшов сюди шукати прізвище функції tidyverse - яке я знав, що існує. Додаючи це для (моїх) майбутніх довідок та для tidyverseентузіастів: purrrlyr:invoke_rows(purrr:invoke_rows у старих версіях).

При підключенні до стандартних методів статистики, як в оригінальному питанні, пакет віників , мабуть, допоможе.


3

@ user20877984 відповідь відмінна. Оскільки вони підсумували це набагато краще, ніж моя попередня відповідь, ось моя (імовірно, все ще хитра) спроба застосування концепції:

Використання do.callв основний моді:

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

Робота над повним набором даних:

# 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

lapplypower.t.testфункції до кожного з рядів заданих значень:

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

Ха-ха заплутався, можливо? ;) чому ти використовуєш t () та застосовуєш над 2, чому не просто застосувати 1?
Рікардо Сапорта,

3

data.table також реально інтуїтивно зрозумілий спосіб зробити це:

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

:=Оператор може бути викликаний в дужках , щоб додати новий стовпець , використовуючи функцію

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

Також легко приймати константи як аргументи, використовуючи цей метод:

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

1

Якщо стовпці data.frame мають різні типи, apply()виникає проблема. Тонкість ітерації рядків полягає в тому, як apply(a.data.frame, 1, ...)відбувається неявне перетворення типів у типи символів, коли стовпці мають різні типи; напр. коефіцієнт та числовий стовпчик. Ось приклад використання коефіцієнта в одному стовпчику для зміни числового стовпця:

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

Віднімання не вдається, оскільки стовпці перетворюються на типи символів.

Одне виправлення полягає у зворотному перетворенні другого стовпця в число:

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

Але конверсій можна уникнути, зберігаючи стовпці окремо та використовуючи mapply():

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

mapply()потрібен, оскільки [[ ]]не приймає векторний аргумент. Отже, ітерацію стовпців можна зробити перед відніманням, передавши вектор [], на трохи потворніший код:

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

1

Дійсно хороша функція для цього adplyз plyr, особливо якщо ви хочете , щоб додати результат до вихідного dataframe. Ця функція та її двоюрідний брат ddplyврятували мені багато головних болів та рядків коду!

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

Крім того, ви можете викликати потрібну вам функцію.

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

може adply () мати справу з функціями, які повертають списки чи фрейми даних? наприклад, що робити, якщо testFunc () повертає список? буде unnest () використовуватись для його вимкнення у додаткові стовпці вашого df_appened?
val
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.