2009-11-09 2 views
129

У меня есть кадр данных, и для каждой строки в этом фреймворке данных я должен выполнить несколько сложных поисков и добавить некоторые данные в файл.Для каждой строки в R-кадре данных

dataFrame содержит научные результаты по отдельным скважинам из 96-луночных планшетов, используемых в биологических исследованиях, так что я хочу сделать что-то вроде:

for (well in dataFrame) { 
    wellName <- well$name # string like "H1" 
    plateName <- well$plate # string like "plate67" 
    wellID <- getWellID(wellName, plateName) 
    cat(paste(wellID, well$value1, well$value2, sep=","), file=outputFile) 
} 

В моем процедурном мире, я бы что-то вроде:

for (row in dataFrame) { 
    #look up stuff using data from the row 
    #write stuff to the file 
} 

Что такое «R-способ» для этого?

+0

Что вы можете задать здесь? Data.frame - это двумерный объект, и цикл над строками - это совершенно нормальный способ делать вещи, поскольку строки обычно являются наборами «наблюдений» «переменных» в каждом столбце. –

+12

что я делаю: for (index in 1: nrow (dataFrame)) {row = dataFrame [index,]; # делайте что-нибудь со строкой}, которая никогда не казалась мне очень симпатичной. –

+1

Вызывает ли getWellID базу данных или что-то еще? В противном случае Джонатан, вероятно, прав, и вы можете процитировать это. – Shane

ответ

91

Вы можете попробовать это, используя функцию apply()

> d 
    name plate value1 value2 
1 A P1  1 100 
2 B P2  2 200 
3 C P3  3 300 

> f <- function(x, output) { 
wellName <- x[1] 
plateName <- x[2] 
wellID <- 1 
print(paste(wellID, x[3], x[4], sep=",")) 
cat(paste(wellID, x[3], x[4], sep=","), file= output, append = T, fill = T) 
} 

> apply(d, 1, f, output = 'outputfile') 
+55

Будьте осторожны, поскольку dataframe преобразуется в матрицу, а то, что вы заканчиваете ('x'), является вектором. Вот почему приведенный выше пример должен использовать числовые индексы, а метод by() дает вам data.frame, что делает ваш код более надежным –

+0

@Darren + 10 за его объявление – 2sb

+0

не работал для меня. Функция apply обрабатывала каждый x, заданный f как значение символа, а не строку. – Zahy

90

Вы можете использовать функцию by():

by(dataFrame, 1:nrow(dataFrame), function(row) dostuff) 

Но итерация по рядам непосредственно, как это редко бывает, что вы хотите; вы должны попытаться вместо этого векторизовать. Могу ли я спросить, что делает фактическая работа в цикле?

+0

обновленный вопрос с дополнительной информацией. благодаря! –

+2

это не сработает, если в кадре данных есть 0 строк, потому что '1: 0' не пуст – sds

+3

Легкое исправление для фрейма 0 строк - использовать [seq_len()] (http://stat.ethz.ch/R -manual/R-devel/library/base/html/seq.html), вставьте 'seq_len (nrow (dataFrame))' вместо '1: nrow (dataFrame)'. – Jim

69

Первой точка Джонатана о векторизации является правильной. Если ваша функция getWellID() является векторизация, то вы можете пропустить петлю и просто использовать кошку или write.csv:

предложение
write.csv(data.frame(wellid=getWellID(well$name, well$plate), 
     value1=well$value1, value2=well$value2), file=outputFile) 

Если getWellID() не векторизации, то рекомендация Джонатана использования by или knguyen-х годах apply должен работать.

В противном случае, если вы действительно хотите использовать for, вы можете сделать что-то вроде этого:

for(i in 1:nrow(dataFrame)) { 
    row <- dataFrame[i,] 
    # do stuff with row 
} 

Вы также можете попробовать использовать foreach пакет, хотя это требует, чтобы вы стали знакомы с этим синтаксисом. Вот простой пример:

library(foreach) 
d <- data.frame(x=1:10, y=rnorm(10)) 
s <- foreach(d=iter(d, by='row'), .combine=rbind) %dopar% d 

Окончательный вариант заключается в использовании функции из plyr пакета, в этом случае соглашение будет очень похоже на функцию применения.

library(plyr) 
ddply(dataFrame, .(x), function(x) { # do stuff }) 
+0

Шейн, спасибо. m не уверен, как написать векторный getWellID. Сейчас мне нужно сделать выбор в существующем списке списков, чтобы найти его или вытащить из базы данных. –

+0

Не стесняйтесь публиковать вопрос getWellID (т. Е. Может ли эта функция быть векторизованной?) Отдельно, и я уверен, что я (или кто-то еще) ответит на нее. – Shane

+2

Даже если getWellID не векторизован, я думаю, вы должны пойти с этим решением и заменить getWellId на 'mapply (getWellId, well $ name, well $ plate)'. –

3

Ну, так как вы попросили R эквивалент другим языкам, я попытался это сделать. Кажется, работает, хотя я на самом деле не смотрел на какой метод является более эффективным в R.

> myDf <- head(iris) 
> myDf 
Sepal.Length Sepal.Width Petal.Length Petal.Width Species 
1   5.1   3.5   1.4   0.2 setosa 
2   4.9   3.0   1.4   0.2 setosa 
3   4.7   3.2   1.3   0.2 setosa 
4   4.6   3.1   1.5   0.2 setosa 
5   5.0   3.6   1.4   0.2 setosa 
6   5.4   3.9   1.7   0.4 setosa 
> nRowsDf <- nrow(myDf) 
> for(i in 1:nRowsDf){ 
+ print(myDf[i,4]) 
+ } 
[1] 0.2 
[1] 0.2 
[1] 0.2 
[1] 0.2 
[1] 0.2 
[1] 0.4 

Для категориальных столбцов, хотя, было бы принести вам Фрейм данных, который вы могли бы напечатанный материал с помощью as.character(), если необходимо.

8

Мне было любопытно, как быстро выполняются не-векторизованные параметры. Для этого я использовал функцию F определяется knguyen

f <- function(x, output) { 
    wellName <- x[1] 
    plateName <- x[2] 
    wellID <- 1 
    print(paste(wellID, x[3], x[4], sep=",")) 
    cat(paste(wellID, x[3], x[4], sep=","), file= output, append = T, fill = T) 
} 

и dataframe, как в его примере:

n = 100; #number of rows for the data frame 
d <- data.frame(name = LETTERS[ sample.int(25, n, replace=T) ], 
        plate = paste0("P", 1:n), 
        value1 = 1:n, 
        value2 = (1:n)*10) 

Я включил две vectorised функции (точно быстрее, чем другие) для того, чтобы сравнить подход кота() с write.table() один ...

library("ggplot2") 
library("microbenchmark") 
library(foreach) 
library(iterators) 

tm <- microbenchmark(S1 = 
         apply(d, 1, f, output = 'outputfile1'), 
        S2 = 
         for(i in 1:nrow(d)) { 
         row <- d[i,] 
         # do stuff with row 
         f(row, 'outputfile2') 
         }, 
        S3 = 
         foreach(d1=iter(d, by='row'), .combine=rbind) %dopar% f(d1,"outputfile3"), 
        S4= { 
         print(paste(wellID=rep(1,n), d[,3], d[,4], sep=",")) 
         cat(paste(wellID=rep(1,n), d[,3], d[,4], sep=","), file= 'outputfile4', sep='\n',append=T, fill = F)       
        }, 
        S5 = { 
         print((paste(wellID=rep(1,n), d[,3], d[,4], sep=","))) 
         write.table(data.frame(rep(1,n), d[,3], d[,4]), file='outputfile5', row.names=F, col.names=F, sep=",", append=T) 
        }, 
        times=100L) 
autoplot(tm) 

Полученное изображение показывает, что применять дает лучшую производительность для не-VEC в то время как write.table(), похоже, превосходит cat(). ForEachRunningTime

13

Я использую эту простую функцию полезности:

rows = function(tab) lapply(
    seq_len(nrow(tab)), 
    function(i) unclass(tab[i,,drop=F]) 
) 

Или более быстрый, менее четкую форму:

rows = function(x) lapply(seq_len(nrow(x)), function(i) lapply(x,"[",i)) 

Эта функция просто расщепляет data.frame к списку строк. Тогда вы можете сделать нормальный «для» по этому списку:

tab = data.frame(x = 1:3, y=2:4, z=3:5) 
for (A in rows(tab)) { 
    print(A$x + A$y * A$z) 
}   

Ваш код от вопроса будет работать с минимальными изменениями:

for (well in rows(dataFrame)) { 
    wellName <- well$name # string like "H1" 
    plateName <- well$plate # string like "plate67" 
    wellID <- getWellID(wellName, plateName) 
    cat(paste(wellID, well$value1, well$value2, sep=","), file=outputFile) 
} 
+1

Почему 'unclass'? –

+0

Это быстрый доступ к прямому списку, а затем к кадру data.frame. –

+1

Просто понял, что еще быстрее сделать одно и то же с двойным lapply: rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i])) –

2

Вы можете использовать функцию by_row из пакета purrrlyr для это:

myfn <- function(row) { 
    #row is a tibble with one row, and the same 
    #number of columns as the original df 
    #If you'd rather it be a list, you can use as.list(row) 
} 

purrrlyr::by_row(df, myfn) 

по умолчанию, возвращаемое значение из myfn помещается в ДФ новый list column называется .out.

Если это единственный вывод, который вы желаете, вы можете написать purrrlyr::by_row(df, myfn)$.out

3

Я думаю, что лучший способ сделать это с основным R является:

for(i in rownames(df)) 
    print(df[i, "column1"]) 

Преимущество над для (я в 1 : nrow (df)) - подход заключается в том, что вы не попадаете в проблему, если df пуст и nrow (df) = 0.

Смежные вопросы