Вызовите функцию apply-like для каждой строки фрейма данных с несколькими аргументами из каждой строки
у меня есть фрейм данных с несколькими столбцами. Для каждой строки в фрейме данных я хочу вызвать функцию в строке, и вход функции использует несколько столбцов из этой строки. Например, допустим, у меня есть эти данные и этот 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, возможно, с применение функции семьи?
Я попытался это:
> 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: фактическая функция, которую я хочу вызвать, - это не простая сумма, но это сила.Т.тест. Я использовал a+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
))
где результатом является вектор выходов для питания.t. тест для каждой строки df.
10 ответов:
вы можете подать заявку
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если вы хотите использовать
testFunctestFunc <- 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']))
A
data.frameэтоlist, так ...на векторизовать функции
do.callобычно это хорошая ставка. Но названия аргументов вступают в игру. Вот вашtestFuncназывается с args x и y вместо a и b....позволяет передавать нерелевантные аргументы без возникновения ошибки:do.call( function(x,z,...) testFunc(x,z), df )на не-векторной функции,
mapplyбудет работать, но вам нужно соответствовать порядку args или явно имя они:mapply(testFunc, df$x, df$z)иногда
applyбудет работать-как когда все args имеют один и тот же тип так принуждаяdata.frameдля матрицы не вызывает проблем при изменении типов данных. Ваш пример был такого рода.если ваша функция должна быть вызвана в другой функции, в которую передаются все аргументы, есть гораздо более гладкий метод, чем эти. Изучите первые линии тела
lm()если вы хотите идти по этому пути.
использовать
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пакетаесли функция, которую вы хотите применить векторизацию, тогда вы могли бы использовать :
> 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к сожалению, как Broeksema Бертян указал,, этот подход сбой, если не использовать все столбцы фрейма данных в
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)
многие функции уже векторизованы, и поэтому нет необходимости в каких-либо итерациях (ни
forпетли или*pplyфункции). ВашtestFuncодин из таких примеров. Вы можете просто позвонить:testFunc(df[, "x"], df[, "z"])в общем, я бы рекомендовал сначала попробовать такие подходы к векторизации и посмотреть, получат ли они ваши ожидаемые результаты.
кроме того, если вам нужно передать несколько аргументов в функцию, которая не векторизованы,
mapplyможет быть, что вам ищем:mapply(power.t.test, df[, "x"], df[, "z"])
Другие правильно указали, что
mapplyсделано для этой цели, но (для полноты картины) концептуально более простой метод-это просто использоватьforпетли.for (row in 1:nrow(df)) { df$newvar[row] <- testFunc(df$x[row], df$z[row]) }
вот альтернативный подход. Это более интуитивно понятно.
один ключевой аспект я чувствую, что некоторые из ответов не учитывались, на что я указываю для потомков, это 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()}))
Я пришел сюда в поисках tidyverse имя функции - которое я знал, что существует. Добавление этого для (моей) будущей ссылки и для
tidyverseэнтузиастов:purrrlyr:invoke_rows(purrr:invoke_rowsв старых версиях).С подключением к стандартным методам статистики, как в исходном вопросе,метла пакет, вероятно, помочь.
@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
lapplythepower.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 ... ...
Если данных.столбцы фреймов бывают разных типов,
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]]])вычитание не выполняется, так как столбцы преобразуются в символьные типы.
одно исправление заключается в обратном преобразовании второго столбца в a номер:
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])
Comments