Как добавить строки в таблице данных R
Я просмотрел StackOverflow, но я не могу найти решение, специфичное для моей проблемы, которое включает добавление строк в кадр данных R.
Я инициализирую пустой фрейм данных в 2 столбца следующим образом.
df = data.frame(x = numeric(), y = character())
затем моя цель-перебрать список значений и на каждой итерации добавить значение в конец списка. Я начал со следующего кода.
for (i in 1:10) {
df$x = rbind(df$x, i)
df$y = rbind(df$y, toString(i))
}
Я также попытался функций c
, append
и merge
без успеха. Пожалуйста, дайте мне знать, если у вас есть какие-либо предложения.
5 ответов
обновление
не зная, что вы пытаетесь сделать, я поделюсь еще одним предложением: предварительно распределите векторы типа, который вы хотите для каждого столбца, вставьте значения в эти векторы, а затем, в конце, создайте свой data.frame
.
продолжение (заранее выделенном data.frame
) как самый быстрый вариант до сих пор, определенный как:
# 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
}
вот аналогичный подход, но тот, где data.frame
создается как последний шаг.
# 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
из пакета" microbenchmark " даст нам более полное представление, чем 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()
(подход ниже) невероятно неэффективен из-за того, как часто он вызывает data.frame
и потому, что растущие объекты таким образом, как правило, медленно в R. f3()
значительно улучшено из-за предварительного распределения, но data.frame
сама структура может быть частью узкого места здесь. f4()
пытается обойти это узкое место без компрометируя подход, который вы хотите принять.
оригинальный ответ
это действительно не очень хорошая идея, но если вы хотите сделать это таким образом, я думаю, вы можете попробовать:
for (i in 1:10) {
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
обратите внимание, что в коде есть еще одна проблема:
- вы должны использовать
stringsAsFactors
если вы хотите, чтобы символы не преобразуются в факторы. Использовать:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
давайте сравним три предложенных решения:
# 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
лучшим решением является предварительное выделение пространства (как это предусмотрено в R). Следующее лучшее решение-использовать list
, и худшим решением (по крайней мере, на основе этих результатов синхронизации) является rbind
.
предположим, что вы просто не знаете размер данных.кадр заранее. Это может быть несколько рядов или несколько миллионов. Вам нужно иметь какой-то контейнер, который растет динамически. Принимая во внимание мой опыт и все связанные с ним ответы, я прихожу с 4 различными решениями:
rbindlist
для сведения.кадриспользовать
data.table
быстроset
операция и соедините ее с ручным удвоением стол, когда это необходимо.использовать
RSQLite
и добавить к таблице, хранящейся в памяти.data.frame
собственная способность расти и использовать пользовательскую среду (которая имеет ссылочную семантику) для хранения данных.кадр, поэтому он не будет скопирован при возврате.
вот тест всех методов для малого и большого количества добавленных строк. Каждый метод имеет 3 функции ассоциируется с ним:
create(first_element)
, который возвращает соответствующий объект поддержки сfirst_element
положить в.append(object, element)
что добавляетelement
до конца таблицы (представленоobject
).access(object)
получаетdata.frame
все элементы.
rbindlist
для сведения.кадр
это довольно легко и прямо вперед:
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
+ при необходимости вручную удваивает таблицу.
я сохраню истинную длину таблицы в .
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 должен быть оптимизирован для быстрой вставки записи, поэтому я изначально возлагал большие надежды на RSQLite
решение
это в основном копирование и вставка Карстен В. ответ на подобную тему.
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
собственный ряд-добавление + пользовательские окружающая среда.
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)
}
набор тестов:
для удобства я буду использовать одну тестовую функцию, чтобы покрыть их все косвенным вызовом. (Я проверил: используя do.call
вместо того, чтобы вызывать функции напрямую, не делает код более измеримым).
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)))
}
давайте посмотрим производительность для N=10 вставок.
я также добавил функции "плацебо" (с суффиксом 0
) , которые ничего не выполняют-просто для измерения накладных расходов тестовая установка.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
для строк 1E5 (измерения сделаны на Intel (R) Core (TM) i7-4710HQ CPU @ 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
похоже, что SQLite-based sulution, хотя и восстанавливает некоторую скорость на больших данных, нигде рядом с данными.таблица + ручной экспоненциальный рост. Разница почти в два порядка величину!
резюме
если вы знаете, что добавите довольно небольшое количество строк (n
для всего остального использовать data.table::set
и выращивать данные.таблица экспоненциально (например, с помощью моего кода).
давайте возьмем вектор "точка", которая имеет номера от 1 до 5
point = c(1,2,3,4,5)
если мы хотим добавить номер 6 в любом месте внутри вектора, то ниже команда может пригодиться
i)векторы
new_var = append(point, 6 ,after = length(point))
ii)столбцы таблицы
new_var = append(point, 6 ,after = length(mtcars$mpg))
команда append
принимает три аргумента:
- столбец вектора/к модифицированный.
- значение, которое будет включено в измененный вектор.
- индекс, после которого значения должны быть добавлены.
простой...!! Извиняюсь на всякий случай...!
более общим решением для Может быть следующее.
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
}
функция extendDf () расширяет фрейм данных с n строками.
пример:
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