Вызовите применимую функцию для каждой строки данных с несколькими аргументами из каждой строки
У меня есть dataframe с несколькими столбцами. Для каждой строки в фрейме данных я хочу вызвать функцию в строке, а вход функции использует несколько столбцов из этой строки. Например, скажем, у меня есть эти данные и этот 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. Есть ли способ сделать это без написания цикла for, возможно, с семейством функций apply?
Я пробовал это:
> 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.
Ответы
Ответ 1
Вы можете применить 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']))
Ответ 2
A data.frame
является list
, поэтому...
Для векторных функций do.call
обычно является хорошей ставкой. Но имена аргументов вступают в игру. Здесь ваш testFunc
вызывается с args x и y вместо a и b. ...
позволяет передавать несоответствующие аргументы без возникновения ошибки:
do.call( function(x,z,...) testFunc(x,z), df )
Для не-векторизованных функций, mapply
будет работать, но вам нужно сопоставить порядок аргументов или явно называть их:
mapply(testFunc, df$x, df$z)
Иногда apply
будет работать - как при всех аргументах одного типа, поэтому принудительное преобразование data.frame
в матрицу не вызывает проблем путем изменения типов данных. Ваш пример был такого рода.
Если ваша функция должна быть вызвана внутри другой функции, в которую все переданные аргументы, существует гораздо более тонкий метод, чем эти. Изучите первые строки тела lm()
, если вы хотите пройти этот маршрут.
Ответ 3
Используйте 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
Ответ 4
Новый ответ с пакетом 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)
Ответ 5
Многие функции уже векторизуются, поэтому нет необходимости в каких-либо итерациях (ни функции for
, ни функции *pply
). Ваш testFunc
- один из таких примеров. Вы можете просто позвонить:
testFunc(df[, "x"], df[, "z"])
В общем, я бы рекомендовал сначала попробовать такие подходы к векторизации и посмотреть, принесут ли вы ваши намеченные результаты.
В качестве альтернативы, если вам нужно передать несколько аргументов функции, которая не является векторизованной, mapply
может быть тем, что вы ищете:
mapply(power.t.test, df[, "x"], df[, "z"])
Ответ 6
Другие правильно указали, что для этой цели создан mapply
, но (для полноты) концептуально более простой метод - просто использовать цикл for
.
for (row in 1:nrow(df)) {
df$newvar[row] <- testFunc(df$x[row], df$z[row])
}
Ответ 7
Вот альтернативный подход. Это более интуитивно понятно.
Один из ключевых аспектов, которые я чувствую, некоторые из ответов не учитывали, что я указываю для потомства, apply() позволяет легко выполнять вычисления строк, но только для матричных (все числовых) данных
операции с столбцами возможны для фреймов данных:
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()}))
Ответ 8
Я пришел сюда в поисках названия функции tidyverse, которое, как я знал, существовало. Добавление этого для (мой) в будущем и для tidyverse
энтузиастов: purrrlyr:invoke_rows
(purrr:invoke_rows
в старых версиях).
С подключением к стандартным методам статистики, как в первоначальном вопросе, пакет метлы, вероятно, помог бы.
Ответ 9
@пользователь20877984 ответ отличная. Поскольку они суммировали его намного лучше, чем мой предыдущий ответ, вот моя (по-видимому, еще неряшливая) попытка применения концепции:
Используя 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
lapply
функция power.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
... ...
Ответ 10
Если столбцы 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])
Ответ 11
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