Вызовите применимую функцию для каждой строки данных с несколькими аргументами из каждой строки
У меня есть dataframe с несколькими столбцами. Для каждой строки в фрейме данных я хочу вызвать функцию в строке, а вход функции использует несколько столбцов из этой строки. Например, допустим, у меня есть эти данные и этот testFunc, который принимает два аргумента:
> df df xyz 1 1 3 5 2 2 4 6 > testFunc <- function(a, b) a + b
Предположим, я хочу применить этот testFunc к столбцам x и z. Итак, для строки 1 я хочу 1 + 5, а для строки 2 я хочу 2 + 6. Есть ли способ сделать это без написания цикла for, возможно, с помощью семейства функций применения?
Я попробовал это:
- Разделить фрейм данных на несколько выходных файлов
- R ggplot2 объединяется с данными шейп-файла и csv для заполнения полигонов
- Последовательность повторяющихся значений в R
- Установка отдельных осевых пределов с помощью facet_wrap и шкал = «бесплатно» в ggplot2
- Как создать двудольную сеть в R с помощью igraph или tnet
> df[,c('x','z')] xz 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.
- Заменить конкретные символы в строках
- фильтр для полных случаев в data.frame с использованием dplyr (случайное удаление)
- Географическое / геопространственное расстояние между 2 списками точек lat / lon (координаты)
- Почему и где \ n символы новой строки вводятся в c ()?
- Как преобразовать имя переменной (object) в String
- Создание пакетов R с использованием альтернативного GCC
- Используя grep в R, чтобы найти строки как целые слова (но не строки как часть слов)
- В чем разница между «1L» и «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]))
РЕДАКТИРОВАТЬ Для доступа к столбцам по имени и не индексу вы можете сделать что-то вроде этого:
testFunc <- function(a, b) a + b apply(dat[,c('x','z')], 1, function(y) testFunc(y['z'],y['x']))
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
использование data.frame
к матрице не вызывает проблем путем изменения типов данных. Ваш пример был такого.
Если ваша функция должна быть вызвана внутри другой функции, в которую все аргументы переданы, существует гораздо более тонкий метод, чем эти. Изучите первые строки тела lm()
если вы хотите пройти этот маршрут.
Использовать mapply
> df <- data.frame(x=c(1,2), y=c(3,4), z=c(5,6)) > df xyz 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) ) xyzf 1 1 3 5 6 2 2 4 6 8
Новый ответ с пакетом 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)
Многие функции уже являются векторизацией, поэтому нет необходимости в каких-либо итерациях (ни for
циклов, ни for
функций *pply
). testFunc
из таких примеров является ваш 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 просто программно просто быть iteratorом для исходного df, тем самым сохраняя память, но R не позволяет ссылаться на указатель или iterator.)
Кроме того, связанный с этим вопрос заключается в том, как работать с каждой отдельной ячейкой в кадре данных.
newdf <- as.data.frame(lapply(df, function(x) {sapply(x, myFunctionForEachCell()}))
Я приехал сюда искать имя функции 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
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 ... ...
Если столбцы 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])