Criação de um dataframe R linha por linha

107

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

Embora isso funcione, parece muito complicado. Não existe uma maneira mais fácil de atingir o mesmo objetivo?

Obviamente, me refiro a casos em que não posso usar alguma applyfunção e preciso criar explicitamente o dataframe linha por linha. Pelo menos, há uma maneira de pushentrar no final de uma lista em vez de manter explicitamente o controle do último índice usado?

David B
fonte
1
Você pode usar append()[que provavelmente deve ter o nome de inserir] ou c()adicionar itens ao final de uma lista, embora não vá ajudá-lo aqui.
Hatmatrix
Não há muitas funções em R que quadros de dados de retorno a menos que você devolvê-los [row-wise] a partir de lapply(), Map()e assim por diante, mas você também pode querer dar uma olhada aggregate(), dapply() {heR.Misc}e cast() {reshape}para ver se as suas tarefas não podem ser tratadas por estes funções (todos estes retornam frames de dados).
Hatmatrix

Respostas:

96

Você pode aumentá-los linha por linha, acrescentando ou usando rbind().

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

Se possível, aloque todo o seu data.frame antecipadamente:

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 então, durante suas operações, insira uma linha de cada vez

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

Isso deve funcionar para data.frame arbitrário e ser muito mais eficiente. Se você ultrapassar N, você sempre poderá reduzir as linhas vazias no final.

Dirk Eddelbuettel
fonte
6
Você não quis dizer colocar N em vez de 10 e listar (1.4, "foo") em vez de c (1.4, "foo") para não forçar o 1.4 a entrar no modo caractere?
hatmatrix
Sim, eu pretendia usar N na criação de data.frame. Além disso, uma ótima pegada com relação à coerção no bate-papo - eu tinha perdido isso.
Dirk Eddelbuettel
1
É melhor editar a resposta do que deixar nos comentários. Eu estava confuso tentando grocar essa resposta.
Usuário
4
data.tableparece ser ainda mais rápido do que a pré-alocação usando data.frames. Testando aqui: stackoverflow.com/a/11486400/636656
Ari B. Friedman
isso ainda é verdade no R 3.1, onde deveria ser mais rápido?
userJT
49

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)
mbq
fonte
3
ele produz uma matriz, não um quadro de dados
Olga
1
@Olga Somente se você vincular linhas de elementos de um tipo igual - BTW, nesse caso é melhor sapply(ou vetorizar) e transpor.
mbq
1
@mbq Exatamente o que estou fazendo. Também descobri que se você inicializá-lo com df <-data.frame (), ele gera um quadro de dados.
Olga
9

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 essa construção com bastante frequência.

hatmatriz
fonte
8

O motivo pelo qual gosto tanto do Rcpp é que nem sempre entendo como o R Core pensa, e com o Rcpp, na maioria das vezes, não preciso.

Falando filosoficamente, você está em um estado de pecado com relação ao paradigma funcional, que tenta garantir que cada valor apareça independente de todos os outros valores; alterar um valor nunca deve causar uma alteração visível em outro valor, como acontece com os ponteiros que compartilham representação em C.

Os problemas surgem quando a programação funcional sinaliza para a pequena nave sair do caminho e a pequena nave responde "Eu sou um farol". Fazer uma longa série de pequenas alterações em um objeto grande que você deseja processar nesse meio tempo coloca você no território do farol.

No C ++ STL, push_back()é um modo de vida. Ele não tenta ser funcional, mas tenta acomodar os idiomas de programação comuns com eficiência .

Com alguma inteligência nos bastidores, às vezes você pode organizar para ter um pé em cada mundo. Os sistemas de arquivos baseados em instantâneos são um bom exemplo (que evoluíram de conceitos como montagens de união, que também dobram os dois lados).

Se o 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 subscritos 1:N, enquanto outra referência ao mesmo armazenamento é válida para subscritos 1:(N+1). Pode haver armazenamento reservado ainda sem referência válida por nada, mas conveniente para uma rápida push_back(). Você não viola o conceito funcional ao anexar fora da faixa que qualquer referência existente considera válida.

Eventualmente, anexando linhas de forma incremental, 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 uso tendem a multiplicar o armazenamento por 2 ao estender a alocação. Pensei ter lido no 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.

No que se refere aos truques nos bastidores, já vi coisas piores. Cada vez que você push_back()cria uma nova linha no dataframe, uma estrutura de índice de nível superior precisa ser copiada. A nova linha pode ser anexada à representação compartilhada sem afetar os valores funcionais antigos. Eu nem acho que complicaria muito o catador de lixo; uma vez que não estou propondo que push_front()todas as referências sejam referências de prefixo à frente do armazenamento vetorial alocado.

Allan Stokes
fonte
2

A resposta de Dirk Eddelbuettel é a melhor; aqui, apenas observo que você pode escapar sem pré-especificar as dimensões do dataframe ou tipos de dados, o que às vezes é útil se você tiver vários tipos de dados e muitas 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(df,row2) #now this works as you'd expect.
John
fonte
Você quis dizer df<-rbind(df, row2)?
Timothy C. Quinn
1

Eu descobri essa maneira de criar dataframe por raw sem matriz.

Com nome de coluna automático

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

Com nome de 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")
    )
phili_b
fonte
0

Se você tem vetores destinados a se tornarem 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))

Reconhecidamente, vejo 2 limitações principais: (1) isso só funciona com dados de modo único e (2) você deve saber suas # colunas finais para que isso funcione (ou seja, estou assumindo que você não está trabalhando com um matriz irregular cujo maior comprimento de linha é desconhecido a priori ).

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

Keegan Smith
fonte
0

Dependendo do formato de sua nova linha, você pode usar tibble::add_rowse sua nova linha for simples e puder ser especificada em "pares de valores". Ou você poderia usar dplyr::bind_rows"uma implementação eficiente do padrão comum de do.call (rbind, dfs)".

Arthur Yip
fonte