2012-09-24 9 views
6

Para establecer los efectos estacionales sobre el uso de energía, necesito alinear la información de uso de energía que tengo de una base de datos de facturación con temperaturas mensuales.División de series de tiempo irregulares en promedios mensuales regulares - R

Estoy trabajando con un conjunto de datos de facturación que tiene facturas de diferentes longitudes y fechas de inicio y finalización, y me gustaría obtener el promedio mensual de cada cuenta dentro de cada mes. Por ejemplo, tengo una base de datos de facturación que tiene las siguientes características:

acct amount  begin  end days 
1 2242 11349 2009-10-06 2009-11-04 29 
2 2242 12252 2009-11-04 2009-12-04 30 
3 2242 21774 2009-12-04 2010-01-08 35 
4 2242 18293 2010-01-08 2010-02-05 28 
5 2243 27217 2009-10-06 2009-11-04 29 
6 2243 117 2009-11-04 2009-12-04 30 
7 2243 14543 2009-12-04 2010-01-08 35 

me gustaría encontrar la manera de coaccionar a estas series de tiempo algo irregular (por cada cuenta) para obtener la cantidad promedio por día dentro de cada mes que es atravesado dentro de cada proyecto de ley, de tal manera que:

acct amount  begin  end days avgamtpday 
1 2242 11349 2009-10-01 2009-10-31 31   X 
2 2242 12252 2009-11-01 2009-11-30 30   X 
3 2242 21774 2009-12-01 2010-12-31 31   X 
4 2242 18293 2010-01-01 2010-01-31 31   X 
4 2242 18293 2010-02-01 2010-02-28 28   X 
5 2243 27217 2009-10-01 2009-10-31 31   X 
6 2243 117 2009-11-01 2009-11-30 30   X 
7 2243 14543 2009-12-01 2009-12-31 30   X 
7 2243 14543 2010-01-01 2010-01-31 31   X 

estoy bastante agnóstica a cualquier herramienta puede hacer esto, ya que sólo tiene que hacerlo una vez.

Una arruga adicional es que la mesa tiene aproximadamente 150,000 filas de largo, lo que no es realmente muy grande para la mayoría de los estándares, pero lo suficientemente grande como para dificultar una solución de lazo en R. He investigado el uso de los paquetes zoo, xts y tempdisagg en R. Empecé a escribir un bucle muy feo que dividiría cada factura, luego creé una fila por cada mes dentro de una factura existente y luego tapply() para resumir por accts y meses, pero sinceramente, no pude ver cómo hacerlo de manera eficiente.

En MySQL, lo he intentado esto:

crear o sustituir vista v3 como seleccionar unión 1 n all select 1 union all select 1;
crear o reemplazar la vista v como seleccionar 1 n de v3 a, v3 b union all seleccionar 1;
conjunto @n = 0;
drop table if exists calendar; crear calendario de tabla (clave primaria de fecha dt);
insertar en el calendario
select cast ('2008-1-1' + intervalo @n: = @ n + 1 día como fecha) como dt de v a, v b, v c, v d, v e, v;

seleccione ACCT, cantidad, inicio, fin, billAmtPerDay, suma (billAmtPerDay), MonthAmt, recuento () Días, suma (billAmtPerDay)/recuento () AverageAmtPerDay, años (dt), el mes (dt) FROM (seleccione *, cantidad/días billAmtPerDay de facturas b calendario de unión interna c en dt entre inicio y fin y comience <> dt) x grupo por acct, cantidad, inicio, fin, fecha de la factura, año (dt), mes (dt);

Pero por razones que no entiendo, a mi servidor no le gusta esta tabla, y se cuelga de la unión interna, incluso cuando realizo los diferentes cálculos. Estoy investigando si hay algún límite de memoria temporal en él.

Gracias!

+1

¿Sus períodos de facturación coinciden con los meses reales, o es alguna función "Día X de cada mes es cuando comienza un nuevo período" tipo de situación? –

+0

Los períodos de facturación son irregulares, por lo que la mayoría de las facturas tienen un promedio de 30 +/- 2 días, pero con algunas facturas de hasta 90 días o más. – bikeclub

+1

Entonces necesitaría algún método para calcular un período de facturación dado solo una fecha, para que pueda hacer la agrupación adecuada. –

Respuesta

8

Aquí es un comienzo usando data.table:

billdata <- read.table(text=" acct amount begin end days 
1 2242 11349 2009-10-06 2009-11-04 29 
2 2242 12252 2009-11-04 2009-12-04 30 
3 2242 21774 2009-12-04 2010-01-08 35 
4 2242 18293 2010-01-08 2010-02-05 28 
5 2243 27217 2009-10-06 2009-11-04 29 
6 2243 117 2009-11-04 2009-12-04 30 
7 2243 14543 2009-12-04 2010-01-08 35", sep=" ", header=TRUE, row.names=1) 

require(data.table) 
DT = as.data.table(billdata) 

En primer lugar, el tipo de cambio de columnas begin y end a fechas. A diferencia de data.frame, esto no copia todo el conjunto de datos.

DT[,begin:=as.Date(begin)] 
DT[,end:=as.Date(end)] 

luego encontrar el lapso de tiempo, se encuentran el proyecto de ley que prevalece para cada día, y el agregado.

alldays = DT[,seq(min(begin),max(end),by="day")] 

setkey(DT, acct, begin) 

DT[CJ(unique(acct),alldays), 
    mean(amount/days,na.rm=TRUE), 
    by=list(acct,month=format(begin,"%Y-%m")), roll=TRUE] 

    acct month  V1 
1: 2242 2009-10 391.34483 
2: 2242 2009-11 406.69448 
3: 2242 2009-12 601.43226 
4: 2242 2010-01 646.27465 
5: 2242 2010-02 653.32143 
6: 2243 2009-10 938.51724 
7: 2243 2009-11 97.36172 
8: 2243 2009-12 375.68065 
9: 2243 2010-01 415.51429 
10: 2243 2010-02 415.51429 

Creo que la lógica de unión predominante es bastante engorrosa en SQL, y más lenta.

Digo que es una pista porque no es del todo correcto. La fila 10 del aviso se repite porque la cuenta 2243 no se extiende hasta 2010-02 a diferencia de la cuenta 2242. Para finalizar, puede rbind en la última fila para cada cuenta y usar rolltolast en lugar de roll. O tal vez crear alldays por cuenta en lugar de en todas las cuentas.

Ver si la velocidad es aceptable en lo anterior, y podemos ir desde allí.

Es probable que golpee un error en 1.8.2 que se ha corregido en 1.8.3. Estoy usando v1.8.3.

Se corrigió el mensaje de error "Interno" cuando se combina unión que contiene grupos faltantes y se agrupa por , # 2162. Por ejemplo: X [Y, .N, by = NonJoinColumn] donde Y contiene algunas filas que no coinciden con X. Este error también puede dar como resultado un error seg .

Avíseme y podremos solucionarlo o actualizar a 1.8.3 desde R-Forge.

Btw, buen ejemplo de datos. Eso hizo que fuera más rápido responder.


Aquí está la respuesta completa aludida anteriormente. Es un poco complicado, tengo que admitirlo, ya que combina varias características de data.table. Esto debería funcionar en 1.8.2, pero solo he probado en 1.8.3.

+0

Hola Matthew, disculpa por no haber respondido antes. He ejecutado ambos métodos, y la solución data.table * es * mucho más rápida, pero dan respuestas diferentes, por lo que estoy revisando el código de ambos en este momento. – bikeclub

+1

@ D.Hsu De un vistazo rápido, creo que la otra respuesta podría ser contar dos veces los días finales de cada factura, ya que los datos de ejemplo podrían considerarse ambiguos. Mi respuesta utiliza [comenzar, finalizar) no [comenzar, finalizar]. –

+0

Matt, lo he comprobado, y tu código funciona bien. La razón por la que me llevó un par de días comprobar que realmente estaba pensando en un cálculo ligeramente diferente al que planteé en el problema. No obstante, la función data.table es muy rápida, y fue relativamente mucho más fácil de modificar para mis fines (eventuales). Gracias por desarrollar el paquete y por responder mi pregunta. – bikeclub

3

Aquí es una manera de hacerlo:

billdata <- read.table(text=" acct amount begin end days 
1 2242 11349 2009-10-06 2009-11-04 29 
2 2242 12252 2009-11-04 2009-12-04 30 
3 2242 21774 2009-12-04 2010-01-08 35 
4 2242 18293 2010-01-08 2010-02-05 28 
5 2243 27217 2009-10-06 2009-11-04 29 
6 2243 117 2009-11-04 2009-12-04 30 
7 2243 14543 2009-12-04 2010-01-08 35", sep=" ", header=TRUE, row.names=1) 

#First, declare your columns "begin" and "end" as dates: 
strptime(billdata$begin, format="%Y-%m-%d") -> billdata$begin 
strptime(billdata$end, format="%Y-%m-%d") -> billdata$end 

#Then create a column with the amount per day on the billing period: 
billdata$avg_on_period<-billdata$amount/billdata$days 

#Then split it into days: 
temp <- data.frame(acct=c(),month=c(),day=c(), avg=c()) 
for(i in 1:nrow(billdata)){ 
    X <- billdata[i,] 
    seq(X$begin,X$end,by="day") -> list_day 
    rbind(temp, data.frame(acct=rep(X$acct,length(list_day)), 
     month=format(list_day, "%Y-%m"), day=format(list_day, "%d"), 
     avg=rep(X$avg_on_period, length(list_day)))) -> temp 
    } 

# And finally merge the different days of the months together: 
output<-aggregate(temp$avg, by=list(temp$month,temp$acct), FUN=mean) 

colnames(output) <- c("Month","Account","Average per day") 

output 
    Month Account Average per day 
1 2009-10 2242  391.34483 
2 2009-11 2242  406.69448 
3 2009-12 2242  595.40000 
4 2010-01 2242  645.51964 
5 2010-02 2242  653.32143 
6 2009-10 2243  938.51724 
7 2009-11 2243  97.36172 
8 2009-12 2243  364.06250 
9 2010-01 2243  415.51429 
+0

Gracias por esta rápida respuesta. He retrasado la respuesta mientras pruebo el código. Sin embargo, tal vez debería haber mencionado antes que quiero aplicar esto a un conjunto de datos relativamente grande: 150,000 filas, y esto solo lo hizo a través de aproximadamente el 10% del código en 5-6 horas (en un servidor muy rápido). Creo que esto probablemente se resuelva mejor usando SQL. – bikeclub

+0

De hecho, si su conjunto de datos tiene 150 000 filas de longitud, el conjunto de datos 'temp' intermedio sería de varios millones de filas. Probablemente haya una buena solución usando 'datos.table' pero nunca lo usé, así que no puedo ayudar aquí. – plannapus

+0

plannapus, @ D.Hsu, sí, es una gran pregunta y es ideal para 'data.table'. Voy a agregar una respuesta más tarde. –

Cuestiones relacionadas