Jak dodawać wiersze do ramki danych R
Rozglądałem się po StackOverflow, ale nie mogę znaleźć rozwiązania specyficznego dla mojego problemu, polegającego na dodawaniu wierszy do ramki danych R.
Inicjalizuję pustą 2-kolumnową ramkę danych, w następujący sposób.
df = data.frame(x = numeric(), y = character())
Następnie, moim celem jest iteracja poprzez listę wartości i, w każdej iteracji, dołączyć wartość do końca listy. Zacząłem od następującego kodu.
for (i in 1:10) {
df$x = rbind(df$x, i)
df$y = rbind(df$y, toString(i))
}
Spróbowałem też funkcjic
, append
, i merge
bez powodzenia. Proszę daj mi znać jeśli masz jakieś sugestie.
5 answers
Update
Nie wiedząc, co próbujesz zrobić, podzielę się jeszcze jedną sugestią: Prealokuj wektory typu, który chcesz dla każdej kolumny, Wstaw wartości do tych wektorów, a następnie, na koniec, utwórz data.frame
.
Kontynuując f3
(prealokowany data.frame
) jako najszybsza opcja do tej pory, zdefiniowana jako:
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(n), y = character(n), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
Oto podobne podejście, ale takie, w którym data.frame
jest tworzony jako ostatni krok.
# Use preallocated vectors
f4 <- function(n) {
x <- numeric(n)
y <- character(n)
for (i in 1:n) {
x[i] <- i
y[i] <- i
}
data.frame(x, y, stringsAsFactors=FALSE)
}
microbenchmark
z "microbenchmark" pakiet da nam bardziej wszechstronny wgląd niż system.time
:
library(microbenchmark)
microbenchmark(f1(1000), f3(1000), f4(1000), times = 5)
# Unit: milliseconds
# expr min lq median uq max neval
# f1(1000) 1024.539618 1029.693877 1045.972666 1055.25931 1112.769176 5
# f3(1000) 149.417636 150.529011 150.827393 151.02230 160.637845 5
# f4(1000) 7.872647 7.892395 7.901151 7.95077 8.049581 5
f1()
(podejście poniżej) jest niewiarygodnie nieefektywne ze względu na to, jak często wywołuje data.frame
i ponieważ rosnące obiekty w ten sposób są generalnie powolne w R. f3()
jest znacznie ulepszone ze względu na prealokację, ale sama struktura data.frame
może być częścią wąskiego gardła tutaj. f4()
próbuje ominąć to wąskie gardło bez narażania podejścia, które chcesz podjąć.
Oryginalna odpowiedź
To jest naprawdę nie jest to dobry pomysł, ale jeśli chcesz to zrobić w ten sposób, myślę, że możesz spróbować: {]}
for (i in 1:10) {
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
Zauważ, że w Twoim kodzie jest jeszcze jeden problem:
- powinieneś użyć
stringsAsFactors
, jeśli chcesz, aby znaki nie zostały przekonwertowane na czynniki. Użycie:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-19 20:10:38
Porównajmy trzy proponowane rozwiązania:
# use rbind
f1 <- function(n){
df <- data.frame(x = numeric(), y = character())
for(i in 1:n){
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
df
}
# use list
f2 <- function(n){
df <- data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
for(i in 1:n){
df[i,] <- list(i, toString(i))
}
df
}
# pre-allocate space
f3 <- function(n){
df <- data.frame(x = numeric(1000), y = character(1000), stringsAsFactors = FALSE)
for(i in 1:n){
df$x[i] <- i
df$y[i] <- toString(i)
}
df
}
system.time(f1(1000))
# user system elapsed
# 1.33 0.00 1.32
system.time(f2(1000))
# user system elapsed
# 0.19 0.00 0.19
system.time(f3(1000))
# user system elapsed
# 0.14 0.00 0.14
Najlepszym rozwiązaniem jest wstępna alokacja przestrzeni (zgodnie z przeznaczeniem w R). Kolejnym najlepszym rozwiązaniem jest użycie list
, A najgorszym rozwiązaniem (przynajmniej na podstawie tych wyników czasowych) wydaje się rbind
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-25 17:07:13
Załóżmy, że po prostu nie znasz rozmiaru danych.klatka z góry. Może to być kilka rzędów lub kilka milionów. Musisz mieć jakiś kontener, który rośnie dynamicznie. Biorąc pod uwagę moje doświadczenie i wszystkie związane z nim odpowiedzi, więc przychodzę z 4 odrębnymi rozwiązaniami: {]}
rbindlist
do danych.ramkaUżyj szybkiej operacji
data.table
set
i połącz ją z ręcznym podwojeniem tabeli, gdy potrzebne.Użyj
RSQLite
i dołącz do tabeli przechowywanej w pamięci.data.frame
'S własne możliwości rozwoju i korzystania z własnego środowiska (które ma semantykę odniesienia) do przechowywania danych.ramka więc nie będzie kopiowana po powrocie.
Oto test wszystkich metod zarówno dla małej, jak i dużej liczby dołączonych wierszy. Każda metoda ma 3 związane z nią funkcje:
create(first_element)
to zwraca odpowiedni obiekt podkładowy zfirst_element
włożonym.append(object, element)
dodajeelement
do końca tabeli (reprezentowanej przezobject
).access(object)
pobieradata.frame
ze wszystkimi wstawionymi elementami.
rbindlist
do danych.frame
To jest dość proste i proste:
create.1<-function(elems)
{
return(as.data.table(elems))
}
append.1<-function(dt, elems)
{
return(rbindlist(list(dt, elems),use.names = TRUE))
}
access.1<-function(dt)
{
return(dt)
}
data.table::set
+ ręczne podwojenie tabeli w razie potrzeby.
Zachowam prawdziwą Długość Tabeli w rowcount
atrybut.
create.2<-function(elems)
{
return(as.data.table(elems))
}
append.2<-function(dt, elems)
{
n<-attr(dt, 'rowcount')
if (is.null(n))
n<-nrow(dt)
if (n==nrow(dt))
{
tmp<-elems[1]
tmp[[1]]<-rep(NA,n)
dt<-rbindlist(list(dt, tmp), fill=TRUE, use.names=TRUE)
setattr(dt,'rowcount', n)
}
pos<-as.integer(match(names(elems), colnames(dt)))
for (j in seq_along(pos))
{
set(dt, i=as.integer(n+1), pos[[j]], elems[[j]])
}
setattr(dt,'rowcount',n+1)
return(dt)
}
access.2<-function(elems)
{
n<-attr(elems, 'rowcount')
return(as.data.table(elems[1:n,]))
}
SQL powinien być zoptymalizowany pod kątem szybkiego wstawiania rekordów, więc początkowo miałem duże nadzieje na rozwiązanieRSQLite
To jest w zasadzie kopiuj i wklej Karsten W. odpowiedź w podobnym wątku.
create.3<-function(elems)
{
con <- RSQLite::dbConnect(RSQLite::SQLite(), ":memory:")
RSQLite::dbWriteTable(con, 't', as.data.frame(elems))
return(con)
}
append.3<-function(con, elems)
{
RSQLite::dbWriteTable(con, 't', as.data.frame(elems), append=TRUE)
return(con)
}
access.3<-function(con)
{
return(RSQLite::dbReadTable(con, "t", row.names=NULL))
}
data.frame
's own row-appending + custom environment.
create.4<-function(elems)
{
env<-new.env()
env$dt<-as.data.frame(elems)
return(env)
}
append.4<-function(env, elems)
{
env$dt[nrow(env$dt)+1,]<-elems
return(env)
}
access.4<-function(env)
{
return(env$dt)
}
Zestaw testów:
Dla wygody użyję jednej funkcji testowej, aby pokryć je wszystkie wywołaniem pośrednim. (Sprawdziłem: używanie do.call
zamiast wywoływania funkcji bezpośrednio nie powoduje wydłużenia działania kodu).
test<-function(id, n=1000)
{
n<-n-1
el<-list(a=1,b=2,c=3,d=4)
o<-do.call(paste0('create.',id),list(el))
s<-paste0('append.',id)
for (i in 1:n)
{
o<-do.call(s,list(o,el))
}
return(do.call(paste0('access.', id), list(o)))
}
Zobaczmy wydajność dla N = 10 insercji.
Dodałem również funkcje "placebo" (z przyrostkiem 0
), które niczego nie wykonują - tylko po to, aby zmierzyć obciążenie zestawu testowego.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
[27]} dla 1E5 wierszy (pomiary wykonywane na procesorze Intel(R) Core(TM) i7-4710HQ @ 2.50 GHz):
nr function time
4 data.frame 228.251
3 sqlite 133.716
2 data.table 3.059
1 rbindlist 169.998
0 placebo 0.202
Wygląda na to, że oparty na SQLite sulucja, chociaż odzyskuje pewną prędkość na dużych danych, nie jest w pobliżu danych.tabela + ręczny wzrost wykładniczy. Różnica jest prawie dwa rzędy wielkości!
Podsumowanie
jeśli wiesz, że dodasz raczej małą liczbę wierszy( N
do wszystkiego innego użyj data.table::set
i powiększyć dane.tabelę wykładniczo (np. używając mojego kodu).
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:23
Weźmy wektor 'punkt', który ma liczby od 1 do 5
point = c(1,2,3,4,5)
Jeśli chcemy dodać liczbę 6 w dowolnym miejscu wewnątrz wektora to poniższe polecenie może się przydać
I) Wektory
new_var = append(point, 6 ,after = length(point))
Ii) Kolumny tabeli
new_var = append(point, 6 ,after = length(mtcars$mpg))
Komenda append
przyjmuje trzy argumenty:
- wektor/kolumna do modyfikacji.
- wartość, którą należy uwzględnić w zmodyfikowanym wektorze.
- indeks dolny, po którym wartości należy dołączyć.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-02-07 13:25:28
Bardziej ogólne rozwiązanie może być następujące.
extendDf <- function (df, n) {
withFactors <- sum(sapply (df, function(X) (is.factor(X)) )) > 0
nr <- nrow (df)
colNames <- names(df)
for (c in 1:length(colNames)) {
if (is.factor(df[,c])) {
col <- vector (mode='character', length = nr+n)
col[1:nr] <- as.character(df[,c])
col[(nr+1):(n+nr)]<- rep(col[1], n) # to avoid extra levels
col <- as.factor(col)
} else {
col <- vector (mode=mode(df[1,c]), length = nr+n)
class(col) <- class (df[1,c])
col[1:nr] <- df[,c]
}
if (c==1) {
newDf <- data.frame (col ,stringsAsFactors=withFactors)
} else {
newDf[,c] <- col
}
}
names(newDf) <- colNames
newDf
}
Funkcja extendDf () rozszerza ramkę danych o n wierszy.
Jako przykład:
aDf <- data.frame (l=TRUE, i=1L, n=1, c='a', t=Sys.time(), stringsAsFactors = TRUE)
extendDf (aDf, 2)
# l i n c t
# 1 TRUE 1 1 a 2016-07-06 17:12:30
# 2 FALSE 0 0 a 1970-01-01 01:00:00
# 3 FALSE 0 0 a 1970-01-01 01:00:00
system.time (eDf <- extendDf (aDf, 100000))
# user system elapsed
# 0.009 0.002 0.010
system.time (eDf <- extendDf (eDf, 100000))
# user system elapsed
# 0.068 0.002 0.070
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-06 15:30:45