Création d'un dataframe R ligne par ligne

107

Je voudrais construire un dataframe ligne par ligne dans R. J'ai fait quelques recherches, et tout ce que j'ai proposé est la suggestion de créer une liste vide, de garder un index de liste scalaire, puis à chaque fois d'ajouter à la liste une trame de données à une seule ligne et avance l'index de la liste d'une unité. Enfin, do.call(rbind,)sur la liste.

Bien que cela fonctionne, cela semble très fastidieux. N'y a-t-il pas un moyen plus simple d'atteindre le même objectif?

Évidemment, je fais référence aux cas où je ne peux pas utiliser une applyfonction et j'ai explicitement besoin de créer le dataframe ligne par ligne. Au moins, y a-t-il un moyen d'arriver à pushla fin d'une liste au lieu de garder explicitement une trace du dernier index utilisé?

David B
la source
1
Vous pouvez utiliser append()[qui devrait probablement être nommé insert] ou c()pour ajouter des éléments à la fin d'une liste, mais cela ne vous aidera pas ici.
hatmatrix le
Il n'y a pas beaucoup de fonctions dans R que les trames de données de retour à moins que vous les renvoient [ligne-sage] à partir lapply(), Map()et ainsi de suite, mais vous pouvez aussi jeter un oeil à aggregate(), dapply() {heR.Misc}et cast() {reshape}pour voir si vos tâches ne peuvent pas être traitées par ces fonctions (toutes renvoient des trames de données).
hatmatrix

Réponses:

96

Vous pouvez les agrandir ligne par ligne en ajoutant ou en utilisant rbind().

Cela ne veut pas dire que vous devriez. La croissance dynamique des structures est l'un des moyens les moins efficaces de coder dans R.

Si vous le pouvez, allouez dès le départ l'intégralité de votre data.frame:

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

puis pendant vos opérations, insérez une ligne à la fois

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

Cela devrait fonctionner pour data.frame arbitraire et être beaucoup plus efficace. Si vous dépassez N, vous pouvez toujours réduire les lignes vides à la fin.

Dirk Eddelbuettel
la source
6
Ne vouliez-vous pas mettre N au lieu de 10, et list (1.4, "foo") au lieu de c (1.4, "foo") pour ne pas forcer le 1.4 en mode caractère?
hatmatrix
Oui, je voulais utiliser N dans la création de data.frame. De plus, très bonne prise sur la coercition dans le chat - j'avais raté ça.
Dirk Eddelbuettel le
1
Il vaudrait mieux modifier la réponse que de la laisser dans les commentaires. J'étais confus en essayant de répondre à cette question.
Utilisateur
4
data.tablesemble être encore plus rapide que la pré-allocation utilisant data.frames. Test ici: stackoverflow.com/a/11486400/636656
Ari
est-ce toujours vrai dans R 3.1 où cela devrait être plus rapide?
userJT
49

On peut ajouter des lignes à NULL:

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

par exemple

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
la source
3
il sort une matrice, pas une trame de données
Olga
1
@Olga Uniquement si vous liez des lignes d'éléments de même type - BTW dans ce cas, il vaut mieux sapply(ou vectoriser) et transposer.
mbq
1
@mbq Exactement ce que je fais. J'ai également constaté que si vous l'initialisez avec df <-data.frame (), il génère une trame de données.
Olga
9

Ceci est un exemple idiot de la façon d'utiliser do.call(rbind,)sur la sortie de Map()[qui est similaire à 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"

J'utilise cette construction assez souvent.

Hatmatrix
la source
8

La raison pour laquelle j'aime tant Rcpp est que je ne comprends pas toujours comment R Core pense, et avec Rcpp, le plus souvent, je n'ai pas à le faire.

Parlant philosophiquement, vous êtes dans un état de péché en ce qui concerne le paradigme fonctionnel, qui essaie de faire en sorte que chaque valeur semble indépendante de toute autre valeur; changer une valeur ne devrait jamais entraîner de changement visible dans une autre valeur, comme vous le faites avec les pointeurs partageant la représentation en C.

Les problèmes surviennent lorsque la programmation fonctionnelle signale au petit vaisseau de s'éloigner du chemin et que le petit vaisseau répond «Je suis un phare». Faire une longue série de petits changements à un gros objet que vous souhaitez traiter entre-temps vous place dans le territoire du phare.

Dans le C ++ STL, push_back()c'est un mode de vie. Il n'essaie pas d'être fonctionnel, mais il essaie de s'adapter efficacement aux idiomes de programmation courants .

Avec une certaine intelligence dans les coulisses, vous pouvez parfois vous arranger pour avoir un pied dans chaque monde. Les systèmes de fichiers basés sur des instantanés en sont un bon exemple (qui a évolué à partir de concepts tels que les montages union, qui agissent également des deux côtés).

Si R Core voulait faire cela, le stockage vectoriel sous-jacent pourrait fonctionner comme un montage d'union. Une référence au stockage vectoriel peut être valide pour les indices 1:N, tandis qu'une autre référence au même stockage est valide pour les indices 1:(N+1). Il pourrait y avoir un stockage réservé pas encore valablement référencé par autre chose que pratique pour un rapide push_back(). Vous ne violez pas le concept fonctionnel lors de l'ajout en dehors de la plage que toute référence existante considère comme valide.

Finalement, en ajoutant des lignes de manière incrémentielle, vous manquez de stockage réservé. Vous devrez créer de nouvelles copies de tout, avec le stockage multiplié par un certain incrément. Les implémentations STL que j'utilise ont tendance à multiplier le stockage par 2 lors de l'extension de l'allocation. Je pensais avoir lu dans R Internals qu'il existe une structure de mémoire où le stockage augmente de 20%. Dans tous les cas, les opérations de croissance se produisent avec une fréquence logarithmique par rapport au nombre total d'éléments ajoutés. Sur une base amortie, cela est généralement acceptable.

Alors que les astuces se déroulent dans les coulisses, j'ai vu pire. Chaque fois que vous push_back()insérez une nouvelle ligne dans la trame de données, une structure d'index de niveau supérieur doit être copiée. La nouvelle ligne pourrait s'ajouter à la représentation partagée sans affecter les anciennes valeurs fonctionnelles. Je ne pense même pas que cela compliquerait beaucoup le ramasse-miettes; puisque je ne propose pas que push_front()toutes les références soient des références de préfixe à l'avant du stockage vectoriel alloué.

Allan Stokes
la source
2

La réponse de Dirk Eddelbuettel est la meilleure; Ici, je note simplement que vous pouvez vous en tirer sans pré-spécifier les dimensions ou les types de données du dataframe, ce qui est parfois utile si vous avez plusieurs types de données et beaucoup de colonnes:

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
la source
Voulez-vous dire df<-rbind(df, row2)?
Timothy C. Quinn le
1

J'ai trouvé cette façon de créer des dataframe par raw sans matrice.

Avec nom de colonne automatique

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

Avec le nom de la colonne

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
la source
0

Si vous avez des vecteurs destinés à devenir des lignes, concaténez-les à l'aide de c(), transmettez-les à une matrice ligne par ligne et convertissez cette matrice en dataframe.

Par exemple, les lignes

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)

peut être converti en une trame de données ainsi:

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

Certes, je vois 2 limitations majeures: (1) cela ne fonctionne qu'avec des données monomodes, et (2) vous devez connaître vos # colonnes finales pour que cela fonctionne (c'est-à-dire que je suppose que vous ne travaillez pas avec un tableau irrégulier dont la plus grande longueur de ligne est inconnue a priori ).

Cette solution semble simple, mais d'après mon expérience avec les conversions de type dans R, je suis sûr que cela crée de nouveaux défis en aval. Quelqu'un peut-il commenter cela?

Keegan Smith
la source
0

Selon le format de votre nouvelle ligne, vous pouvez utiliser tibble::add_rowsi votre nouvelle ligne est simple et peut être spécifiée en "paires de valeurs". Ou vous pouvez utiliser dplyr::bind_rows"une implémentation efficace du modèle commun de do.call (rbind, dfs)".

Arthur Yip
la source