studio - superponer graficas en r
Cómo agregar filas a un marco de datos R (5)
Actualizar
Sin saber lo que estás tratando de hacer, compartiré una sugerencia más: asigna previamente vectores del tipo que deseas para cada columna, inserta valores en esos vectores y luego, al final, crea tu data.frame
.
Continuando con Julian''s f3
(un data.frame
preasignado) como la opción más rápida hasta el momento, definida como:
# 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
}
Aquí hay un enfoque similar, pero uno donde data.frame
se crea como el último paso.
# 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
del paquete "microbenchmark" nos dará una visión más completa que 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()
(el enfoque a continuación) es increíblemente ineficiente debido a la frecuencia con que se llama data.frame
y porque el crecimiento de objetos de esa manera es generalmente lento en R. f3()
es mucho mejor debido a la data.frame
, pero la estructura de data.frame
sí misma ser parte del cuello de botella aquí. f4()
intenta eludir ese cuello de botella sin comprometer el enfoque que desea tomar.
Respuesta original
Esto realmente no es una buena idea, pero si quisieras hacerlo de esta manera, supongo que puedes intentarlo:
for (i in 1:10) {
df <- rbind(df, data.frame(x = i, y = toString(i)))
}
Tenga en cuenta que en su código, hay otro problema:
- Debería usar
stringsAsFactors
si desea que los caracteres no se conviertan en factores. Uso:df = data.frame(x = numeric(), y = character(), stringsAsFactors = FALSE)
He buscado en StackOverflow, pero no encuentro una solución específica para mi problema, que implica agregar filas a un marco de datos R.
Estoy inicializando un marco de datos de 2 columnas vacías, de la siguiente manera.
df = data.frame(x = numeric(), y = character())
Luego, mi objetivo es recorrer una lista de valores y, en cada iteración, agregar un valor al final de la lista. Empecé con el siguiente código.
for (i in 1:10) {
df$x = rbind(df$x, i)
df$y = rbind(df$y, toString(i))
}
También intenté las funciones c
, append
y merge
sin éxito. Por favor, hágamelo saber si tiene alguna sugerencia.
Comparemos las tres soluciones propuestas:
# 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
La mejor solución es preasignar el espacio (como se pretende en R). La siguiente mejor solución es usar la list
, y la peor solución (al menos en función de estos resultados de tiempo) parece ser la de rbind
.
Supongamos que simplemente no conoce el tamaño del data.frame por adelantado. Puede ser unas pocas filas, o algunos millones. Necesita tener algún tipo de contenedor, que crezca dinámicamente. Tomando en consideración mi experiencia y todas las respuestas relacionadas en SO, vengo con 4 soluciones distintas:
rbindlist
a data.frameUsa la operación de
set
rápida dedata.table
ydata.table
con la duplicación manual de la tabla cuando sea necesario.Use
RSQLite
y anexe a la tablaRSQLite
en la memoria.La capacidad de
data.frame
para crecer y usar un entorno personalizado (que tiene semántica de referencia) para almacenar el data.frame para que no se copie a la vuelta.
Aquí hay una prueba de todos los métodos para una gran cantidad de filas adjuntas. Cada método tiene 3 funciones asociadas a él:
create(first_element)
que devuelve el objeto de respaldo apropiado confirst_element
put in.append(object, element)
que agrega elelement
al final de la tabla (representado por elobject
).access(object)
obtiene eldata.frame
con todos los elementos insertados.
rbindlist
a data.frame
Eso es bastante fácil y directo:
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
+ doblando manualmente la tabla cuando sea necesario.
Almacenaré la verdadera longitud de la tabla en un atributo rowcount
.
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 debe optimizarse para la inserción rápida de registros, por lo que inicialmente tenía grandes esperanzas para la solución RSQLite
Esto es básicamente copiar y pegar de Karsten W. responder en un hilo similar.
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))
}
entorno personal personalizado de fila de datos de 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)
}
El conjunto de pruebas:
Para mayor comodidad, usaré una función de prueba para cubrirlos todos con llamadas indirectas. (Lo comprobé: el uso de do.call
lugar de llamar a las funciones directamente no hace que el código se pueda medir durante más tiempo).
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)))
}
Veamos el rendimiento para n = 10 inserciones.
También agregué funciones de "placebo" (con el sufijo 0
) que no realizan nada, solo para medir la sobrecarga de la configuración de la prueba.
r<-microbenchmark(test(0,n=10), test(1,n=10),test(2,n=10),test(3,n=10), test(4,n=10))
autoplot(r)
Para filas 1E5 (mediciones realizadas en Intel (R) Core (TM) i7-4710HQ CPU a 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
Parece que el sulution basado en SQLite, aunque recupera algo de velocidad en datos grandes, no se acerca al crecimiento exponencial manual de data.table +. ¡La diferencia es casi dos órdenes de magnitud!
Resumen
Si sabe que va a agregar un número bastante pequeño de filas (n <= 100), siga adelante y use la solución más simple posible: simplemente asigne las filas al data.frame usando la notación de corchetes e ignore el hecho de que data.frame es no pre-poblado
Para todo lo demás, use data.table::set
y haga crecer data.table exponencialmente (por ejemplo, usando mi código).
Tomemos un vector ''punto'' que tiene números del 1 al 5
point = c(1,2,3,4,5)
si queremos agregar un número 6 en cualquier lugar dentro del vector, entonces el comando debajo puede ser útil
i) Vectores
new_var = append(point, 6 ,after = length(point))
ii) columnas de una mesa
new_var = append(point, 6 ,after = length(mtcars$mpg))
El comando append
toma tres argumentos:
- el vector / columna a ser modificado.
- valor que se incluirá en el vector modificado.
- un subíndice, después del cual los valores deben ser anexados.
sencillo...!! Disculpas en caso de ...
Una solución más genérica para podría ser la siguiente.
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
}
La función extendDf () extiende un marco de datos con n filas.
Como ejemplo:
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