Для кожного рядка в рамці даних R


173

У мене є фрейм даних, і для кожного рядка в цьому кадрі я повинен робити складні пошуки і додавати деякі дані до файлу.

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
}

Який "шлях" це зробити?


Яке тут ваше запитання? Фрейм data.frame - це двовимірний об'єкт, і цикління за рядками - це абсолютно нормальний спосіб робити речі, оскільки рядки - це звичайно набори "спостережень" змінних у кожному стовпці.
Дірк Еддельбюттель

16
Що я в кінцевому підсумку роблю так: for (індекс в 1: nrow (dataFrame)) {row = dataFrame [index,]; # робити речі з рядком}, які мені ніколи не здавалися дуже гарними.
Карл Корилл-Мартін

1
Чи отримує getWellID базу даних чи щось таке? Інакше Джонатан, мабуть, правий, і ти міг би це векторизувати.
Шейн

Відповіді:


103

Ви можете спробувати це, використовуючи 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')

76
Будьте обережні, оскільки кадр даних перетворюється на матрицю, а те, що ви в кінцевому підсумку отримуєте ( x) - вектор. Ось чому у наведеному прикладі доводиться використовувати числові індекси; підхід by () надає вам data.frame, що робить ваш код більш надійним.
Даррен Кук

не працював для мене. Функція застосовувати розглядає кожне x, задане f як значення символу, а не рядок.
Zahy

3
Зауважте також, що ви можете посилатися на стовпці за назвою. Отже: wellName <- x[1]теж могло бути wellName <- x["name"].
founddrama

1
Коли Даррен згадав про надійний, він мав на увазі щось на зразок зміщення порядків стовпців. Ця відповідь не спрацює, тоді як відповідь з () все ще працюватиме.
HelloWorld

120

Ви можете використовувати by()функцію:

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

Але ітерація над рядами безпосередньо, як це, рідко є тим, що ви хочете; вам слід спробувати векторизувати замість цього. Чи можу я запитати, що робить фактична робота в циклі?


5
це не спрацює, якщо кадр даних має 0 рядків, оскільки 1:0він не порожній
sds

10
Простий виправлення для випадку рядка 0 - це використання seq_len () , що вставляється seq_len(nrow(dataFrame))замість 1:nrow(dataFrame).
Jim Jim

13
Як ви реально реалізуєте (рядок)? Це стовпчик фреймів $? фрейм даних [somevariableNamehere]? Як ви насправді говорите його ряд. Псевдокод "функція (рядок) dostuff", як би це насправді виглядало?
uh_big_mike_boi

1
@Mike, зміни dostuffв цій відповіді на " str(row) Ви побачите кілька рядків, надрукованих на консолі, починаючи з " "data.frame": 1 obs з x змінних. " Але будьте обережні, змінюючи dostuffдо rowне повертає об'єкт data.frame для зовнішньої функції в цілому. Натомість він повертає список кадрів даних з одним рядком.
pwilcox

91

По-перше, точка Джонатана щодо векторизації є правильною. Якщо ваша функція getWellID () векторизована, ви можете пропустити цикл і просто використовувати cat або 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 })

Шейн, дякую. Я не впевнений, як написати векторизований getWellID. Що мені зараз потрібно зробити - це заглибитись у існуючий список списків, щоб переглянути його або витягнути з бази даних.
Карл Корилл-Мартін

Не соромтеся розміщувати питання getWellID (тобто чи можна цю функцію векторизувати?) Окремо, і я впевнений, що я (або хтось інший) відповім на нього.
Шейн

2
Навіть якщо getWellID не векторизований, я думаю, що вам слід скористатися цим рішенням і замінити getWellId на mapply(getWellId, well$name, well$plate).
Джонатан Чанг

Навіть якщо ви витягнете його з бази даних, ви можете витягнути їх усі відразу, а потім відфільтрувати результат у R; це буде швидше, ніж ітеративна функція.
Шейн

+1 для foreach- я буду використовувати пекло з цього.
Джош Боде

20

Я думаю, що найкращий спосіб зробити це за допомогою базового R:

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

Перевага перед-- for( i in 1:nrow(df))підходом полягає в тому, що ви не потрапляєте в проблеми, якщо dfпорожній і nrow(df)=0.


17

Я використовую цю просту функцію утиліти:

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

Швидше отримати доступ до прямого списку, ніж до data.frame.
Ł nianiewski-Wołłk

1
Щойно зрозумів, що ще швидше зробити те ж саме з подвійною лаплою: rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i]))
Ł Nianiewski-Wołłk

Отже внутрішній lapplyітератор над стовпцями всього набору даних x, даючи ім'я кожному стовпцю c, а потім витягуючи цей iзапис із цього вектора стовпців. Це правильно?
Аарон Макдейд

Дуже хороша! У моєму випадку, я повинен був перетворити з значень «фактора» до базового значення: wellName <- as.character(well$name).
Стів Пітчерс

9

Мені було цікаво про час роботи невекторизованих варіантів. Для цього я використав функцію 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)
}

і кадр даних, як той у його прикладі:

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 )

Я включив дві векторизовані функції (напевне, швидше, ніж інші), щоб порівняти підхід cat () з 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)

Отримане зображення показує, що застосовано дає найкращі показники для невекторизованої версії, тоді як write.table (), здається, перевершує кота (). ForEachRunningTime


6

Ви можете використовувати 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кладеться у новий стовпець списку у df, що називається .out.

Якщо це єдиний вихід, який ви бажаєте, ви можете написати purrrlyr::by_row(df, myfn)$.out


2

Ну, оскільки ви попросили 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 (), якщо потрібно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.