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
}

이것을하는 "R 방법"은 무엇입니까?


당신의 질문은 무엇입니까? data.frame은 2 차원 객체이며 행을 반복하는 것은 행이 일반적으로 각 열의 '변수'에 대한 '관찰'세트이기 때문에 작업을 수행하는 완벽한 정상적인 방법입니다.
Dirk Eddelbuettel

16
내가하는 일은 : for (index in 1 : nrow (dataFrame)) {row = dataFrame [index,]; # 행으로 물건을 처리}} 나에게 아주 예쁘게 보이지 않았다.
Carl Coryell-Martin

1
getWellID가 데이터베이스 또는 다른 것을 호출합니까? 그렇지 않으면 Jonathan이 옳을 것이며 이것을 벡터화 할 수 있습니다.
Shane

답변:


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을 제공하여 코드를 더욱 강력하게 만듭니다.
대런 쿡

나를 위해 일하지 않았다. apply 함수는 f에 주어진 모든 x를 행이 아닌 문자 값으로 취급합니다.
Zahy

3
이름별로 열을 참조 할 수도 있습니다. 그래서 : wellName <- x[1]또한 될 수 있습니다 wellName <- x["name"].
founddrama

1
대런이 견고하다고 언급했을 때, 그는 기둥의 순서를 바꾸는 것과 같은 것을 의미했습니다. 이 답변은 작동하지 않지만 by () 사용하면 여전히 작동합니다.
HelloWorld

120

by()기능을 사용할 수 있습니다 :

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

그러나 이와 같이 행을 직접 반복하는 것은 거의 원하지 않습니다. 대신 벡터화를 시도해야합니다. 루프의 실제 작업이 무엇인지 물어볼 수 있습니까?


5
1:0비어 있지 않기 때문에 데이터 프레임에 0 개의 행이있는 경우이 기능이 제대로 작동 하지 않습니다.
sds

10
0 행 경우에 대한 쉬운 수정은 seq_len () 을 사용 seq_len(nrow(dataFrame))하는 대신에 삽입 하는 것입니다 1:nrow(dataFrame).
Jim

13
실제로 어떻게 구현합니까 (행)? dataframe $ column입니까? 데이터 프레임 [somevariableNamehere]? 실제로 행을 어떻게 말합니까? 의사 코드 "function (row) dostuff"실제로 어떻게 보일까요?
uh_big_mike_boi

1
@Mike, dostuff이 답변을 다음으로 변경 str(row) 하십시오. " 'data.frame': 1 개의 obs of x 변수"로 시작하는 콘솔에 여러 줄이 인쇄되어 있습니다. 그러나 변화,주의 dostuffrow전체 외부 기능에 대한 data.frame 객체를 반환하지 않습니다. 대신 하나의 행 데이터 프레임 목록을 반환합니다.
pwilcox

91

먼저 벡터화에 대한 Jonathan의 요점은 맞습니다. getWellID () 함수가 벡터화 된 경우 루프를 건너 뛰고 cat 또는 write.csv를 사용할 수 있습니다.

write.csv(data.frame(wellid=getWellID(well$name, well$plate), 
         value1=well$value1, value2=well$value2), file=outputFile)

getWellID ()가 벡터화되지 않은 경우 Jonathan 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를 작성하는 방법을 잘 모르겠습니다. 지금 당장해야 할 일은 기존 목록 목록을 찾아 데이터베이스를 찾아 보거나 끌어내는 것입니다.
Carl Coryell-Martin

getWellID 질문을 자유롭게 게시하십시오 (즉,이 함수를 벡터화 할 수 있습니까?). 본인 (또는 다른 사람)이 답변 할 것이라고 확신합니다.
셰인

2
getWellID가 벡터화되지 않더라도이 솔루션을 사용하여 getWellId를로 대체해야한다고 생각합니다 mapply(getWellId, well$name, well$plate).
Jonathan Chang

데이터베이스에서 가져 오더라도 한 번에 가져 와서 결과를 R로 필터링 할 수 있습니다. 반복 함수보다 빠릅니다.
셰인

+1- foreach나는 그 지옥에서 지옥을 사용할 것입니다.
Josh Bode

20

기본 R로 이것을 수행하는 가장 좋은 방법은 다음과 같습니다.

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

접근 방식에 비해 장점 은 비어있는 for( i in 1:nrow(df))경우 문제가 발생하지 않는다는 것 입니다.dfnrow(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을 행 목록으로 분할합니다. 그런 다음이 목록에서 일반적인 "for"를 만들 수 있습니다.

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

데이터 목록보다 직선 목록에 액세스하는 것이 더 빠릅니다.
Ł Łaniewski-Wołłk

1
double lapply로 같은 것을 만드는 것이 훨씬 빠릅니다 : rows = function (x) lapply (seq_len (nrow (x)), function (i) lapply (x, function (c) c [i]))
Ł Łaniewski-Wołłk

따라서 내부 lapply는 전체 데이터 집합의 열을 반복 x하여 각 열에 이름을 부여한 c다음 i해당 열 벡터에서 th 항목 을 추출합니다 . 이 올바른지?
Aaron McDaid

아주 좋아요! 제 경우에는 "인자"값에서 기본 값으로 변환해야했습니다 wellName <- as.character(well$name).
Steve Pitchers

9

벡터화되지 않은 옵션의 시간 성능이 궁금합니다. 이를 위해 knguyen에서 정의한 함수 f를 사용했습니다.

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)

결과 이미지는 apply가 벡터화되지 않은 버전에 대해 최상의 성능을 제공하는 반면 write.table ()은 cat ()보다 성능이 우수함을 보여줍니다. 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)

기본적으로에서 반환 된 값은 이라는 df myfn의 새 목록 열에 저장 됩니다 .out.

이것이 당신이 원하는 유일한 출력이라면, 당신은 쓸 수 있습니다. purrrlyr::by_row(df, myfn)$.out


2

글쎄, 당신이 다른 언어와 동등한 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 ()를 사용하여 유형 변환 할 수있는 데이터 프레임을 가져옵니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.