ti-enxame.com

Criando um dataframe R, linha por linha

Eu gostaria de construir um dataframe linha por linha em R. Eu fiz algumas pesquisas, e tudo que surgiu foi a sugestão para criar uma lista vazia, manter um escalar de índice de lista, então cada vez adicionar à lista um dataframe de linha única e avançar o índice da lista em um. Finalmente, do.call(rbind,) na lista.

Enquanto isso funciona, parece muito complicado. Não há uma maneira mais fácil de alcançar o mesmo objetivo?

Obviamente, eu me refiro a casos em que não posso usar alguma função apply e explicitamente preciso criar o dataframe linha por linha. Pelo menos, existe uma maneira de Push no final de uma lista em vez de explicitamente acompanhar o último índice usado?

98
David B

Você pode aumentá-las linha por linha adicionando ou usando rbind()

Isso não significa que você deveria. Estruturas em crescimento dinâmico é uma das maneiras menos eficientes de codificar em R.

Se puder, aloque todo o seu data.frame na frente:

N <- 1e4  # total number of rows to preallocate--possibly an overestimate

DF <- data.frame(num=rep(NA, N), txt=rep("", N),  # as many cols as you need
                 stringsAsFactors=FALSE)          # you don't know levels yet

e depois, durante suas operações, insira a linha de cada vez

DF[i, ] <- list(1.4, "foo")

Isso deve funcionar para dados arbitrários e ser muito mais eficiente. Se você ultrapassou N, pode sempre reduzir as linhas vazias no final.

88
Dirk Eddelbuettel

Pode-se adicionar linhas a NULL:

df<-NULL;
while(...){
  #Some code that generates new row
  rbind(df,row)->df
}

por exemplo

df<-NULL
for(e in 1:10) rbind(df,data.frame(x=e,square=e^2,even=factor(e%%2==0)))->df
print(df)
46
mbq

Este é um exemplo bobo de como usar do.call(rbind,) na saída de Map() [que é semelhante a lapply()]

> DF <- do.call(rbind,Map(function(x) data.frame(a=x,b=x+1),x=1:3))
> DF
  x y
1 1 2
2 2 3
3 3 4
> class(DF)
[1] "data.frame"

Eu uso esse constructo com bastante frequência.

9
hatmatrix

A razão pela qual eu gosto muito do Rcpp é que nem sempre eu entendo o que o R Core pensa, e com o Rcpp, na maioria das vezes, eu não preciso. 

Falando filosoficamente, você está em um estado de pecado com respeito ao paradigma funcional, que tenta assegurar que todo valor pareça seja independente de qualquer outro valor; alterar um valor nunca deve causar uma alteração visível em outro valor, a maneira como você obtém os ponteiros que compartilham a representação em C. 

Os problemas surgem quando a programação funcional sinaliza a pequena embarcação para sair do caminho, e a pequena embarcação responde: "Sou um farol". Fazer uma longa série de pequenas mudanças em um objeto grande que você deseja processar enquanto isso o coloca em um território de farol. 

No C++ STL, Push_back() é um modo de vida. Ele não tenta ser funcional, mas tenta acomodar idiomas de programação comuns eficientemente

Com alguma esperteza nos bastidores, às vezes você pode ter um pé em cada mundo. Os sistemas de arquivos baseados em captura instantânea são um bom exemplo (que evoluiu de conceitos como montagens de união, que também exercem ambos os lados). 

Se R Core quisesse fazer isso, o armazenamento vetorial subjacente poderia funcionar como uma montagem de união. Uma referência ao armazenamento vetorial pode ser válida para os subscritos 1:N, enquanto outra referência para o mesmo armazenamento é válida para os subscritos 1:(N+1). Poderia haver armazenamento reservado ainda não referenciado validamente por qualquer coisa que não fosse conveniente para um Push_back() rápido. Você não viola o conceito funcional ao anexar fora do intervalo que qualquer referência existente considera válida. 

Eventualmente acrescentando linhas incrementalmente, você fica sem armazenamento reservado. Você precisará criar novas cópias de tudo, com o armazenamento multiplicado por algum incremento. As implementações de STL que eu uso tendem a multiplicar o armazenamento por 2 ao estender a alocação. Eu pensei que eu li em R Internals que existe uma estrutura de memória onde o armazenamento aumenta em 20%. De qualquer maneira, as operações de crescimento ocorrem com frequência logarítmica em relação ao número total de elementos anexados. Em uma base amortizada, isso geralmente é aceitável. 

Como truques nos bastidores vão, eu vi pior. Toda vez que você Push_back() uma nova linha no dataframe, uma estrutura de índice de nível superior precisaria ser copiada. A nova linha pode ser anexada à representação compartilhada sem afetar nenhum valor funcional antigo. Eu nem acho que isso complicaria muito o coletor de lixo; já que não estou propondo Push_front() todas as referências são referências de prefixo para a frente do armazenamento vetorial alocado. 

8
Allan Stokes

A resposta de Dirk Eddelbuettel é a melhor; aqui, apenas observe que você pode se safar com a não especificação prévia das dimensões ou dos tipos de dados do dataframe, o que às vezes é útil se você tiver vários tipos de dados e várias colunas:

row1<-list("a",1,FALSE) #use 'list', not 'c' or 'cbind'!
row2<-list("b",2,TRUE)  

df<-data.frame(row1,stringsAsFactors = F) #first row
df<-rbind(d,row2) #now this works as you'd expect.
1
John

Se você tiver vetores destinados a se tornar linhas, concatene-os usando c(), passe-os para uma matriz linha por linha e converta essa matriz em um dataframe.

Por exemplo, linhas

dummydata1=c(2002,10,1,12.00,101,426340.0,4411238.0,3598.0,0.92,57.77,4.80,238.29,-9.9)
dummydata2=c(2002,10,2,12.00,101,426340.0,4411238.0,3598.0,-3.02,78.77,-9999.00,-99.0,-9.9)
dummydata3=c(2002,10,8,12.00,101,426340.0,4411238.0,3598.0,-5.02,88.77,-9999.00,-99.0,-9.9)

pode ser convertido em um quadro de dados assim:

dummyset=c(dummydata1,dummydata2,dummydata3)
col.len=length(dummydata1)
dummytable=data.frame(matrix(data=dummyset,ncol=col.len,byrow=TRUE))

Admito que vejo duas grandes limitações: (1) isso só funciona com dados de modo único, e (2) você deve conhecer suas # colunas finais para que isso funcione (ou seja, suponho que você não esteja trabalhando com um matriz irregular cuja maior comprimento de linha é desconhecida a priori ).

Esta solução parece simples, mas pela minha experiência com conversões de tipo em R, tenho certeza que isso cria novos desafios. Alguém pode comentar sobre isso?

0
Keegan Smith

Eu encontrei este caminho para criar dataframe por cru sem matriz.

Com o nome da coluna automática

df<-data.frame(
        t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
        ,row.names = NULL,stringsAsFactors = FALSE
    )

Com o nome da coluna

df<-setNames(
        data.frame(
            t(data.frame(c(1,"a",100),c(2,"b",200),c(3,"c",300)))
            ,row.names = NULL,stringsAsFactors = FALSE
        ), 
        c("col1","col2","col3")
    )
0
phili_b