Monday, February 18, 2013

Cuando SAP HANA conocio a R - Que hay de nuevo?


Desde que escribi mi blog Cuando SAP HANA conocio a R - El primer beso he recibido muchos buenos comentarios...y uno de esos comentarios fue..."Que hay de nuevo?"...

Bueno...como ya deben saberlo SAP HANA trabaja con R utilizando Rserve, un paquete que permite comunicarse con un servidor R, asi que realmente...no pueden haber muchas cosas nuevas...pero...lo bueno es que SAP HANA ha sido probado con R 2.15 y Rserve 0.6-8 asi que cualquier nueva caracteristica agregada en R o Rserve es instaneamente disponible en SAP HANA -;)

Pero! No escribiria un blog si que no hubiera al menos una nueva caracteristica, no? Pueden leer mas sobre eso aqui SAP HANA R Integration Guide.

Por supuesto...para esto necesitas SAP HANA rev. 48 (SPS5)

Asi que, cual es la nueva caracteristica? Bueno...puedes guardar un modelo de entrenamiento como lm() o ksvm() directamente en la base de datos para usarla despues. Esto es realmente excelente, porque si tienes un calculo muy grande y complejo que hacer, solo necesitas guardar el modelo y utilizarlo despues sin tener que volver a hacer todo el procesamiento nuevamente.

Hagamos un ejemplo...y espero que todos los fanaticos de R no me maten por esto...porque cuando se trata de Estadistica...estoy realmente perdido en el bosque -:(

Digamos que tenemos dos tablas del paquete SFLIGHT...SPFLI y STICKET, asi que queremos predecir cuantas veces un cliente va a viajar a diferentes destinos (CITYFROM-CITYTO) dependiendo de cuantas veces todos los clientes han viajado a los mismos destinos.

Vamos a crear un archivo SQLScript para obtener la informacion, transformarla, crear el model y guardarlo en la base de datos...

Build_Flight_Model.sql
--Creamos un TYPE T_FLIGHTS y tomamos la informacion de las tablas SPFLI y STICKET.
 
DROP TYPE T_FLIGHTS;
CREATE TYPE T_FLIGHTS AS TABLE (
CARRID NVARCHAR(3),
CUSTOMID NVARCHAR(8),
CITYFROM NVARCHAR(20),
CITYTO NVARCHAR(20)
);
 
--Creamos un TYPE FLIGHT_MODEL_T y una tabla FLIGHT_MODEL para guardar el modelo
--en la base de datos
 
DROP TYPE FLIGHT_MODEL_T;
CREATE TYPE FLIGHT_MODEL_T AS TABLE (
ID INTEGER,
DESCRIPTION VARCHAR(255),
MODEL BLOB
);
 
DROP TABLE FLIGHT_MODEL;
CREATE COLUMN TABLE FLIGHT_MODEL (
ID INTEGER,
DESCRIPTION VARCHAR(255),
MODEL BLOB
);
 
--Este procedimiento R va a recibir la informacion de T_FLIGHTS, 
--crear una tabla con el campo FLIGHT_NAME que a contener la concatenacion de
--los campos CARRID, CITYFROM and CITYTO. ejm: AA-NEW YORK-SAN FRANCISCO.
--Vamos a convertir la tabla en un data.frame, asi que todos los valores iguales en FLIGHT_NAME
--van a ser sumarizados.
--Utilizamos la funcion subset(), vamos deshacernos de todos los FLIGHT_NAME's que tienen una frequencia 
--menor o igual que 0.
--Vamos a utilizar la funcion nrow() para contar todas las ocurrencias de FLIGHT_NAME y multiplicarla por 
--10 (Lo guardamos en f_rows)
--Vamos a utilizar la funcion sum() para sumar todas las frequencias y luego dividirlas por f_rows 
--(Lo guardamos en f_sum)
--Vamos a utilizar la funcion mapply() para dividir cada una las frecuencias por f_sum
--Vamos a utilizar la funcion order() para ordenar por FLIGHT_NAME
--Vamos a utilizar la funcion colnames() para asignar nombres a nuestro data.frame
--Vamos a utilizar la funcion lm() para generar una Regresion Lineal basada en el FLIGHT_NAME 
--y su frecuencia
--Finalmente, vamos a utilizar la funcion personalizada generateRobjColumn() para guardar el resultado 
--del modelo en el buffer.

DROP PROCEDURE FLIGHT_TRAIN_PROC;
CREATE PROCEDURE FLIGHT_TRAIN_PROC (IN traininput "T_FLIGHTS", OUT modelresult FLIGHT_MODEL_T)
LANGUAGE RLANG AS
BEGIN
generateRobjColumn <- function(...){
          result <- as.data.frame(cbind(
                    lapply(
                              list(...),
                              function(x) if (is.null(x)) NULL else serialize(x, NULL)
                    )
          ))
          names(result) <- NULL
          names(result[[1]]) <- NULL
          result
}
tab<-table(FLIGHT_NAME=paste(traininput$CARRID,traininput$CITYFROM,traininput$CITYTO,sep="-"))
df<-data.frame(tab)
ss<-subset(df,(df$Freq>0))
freq<-ss$Freq
f_rows<-(nrow(ss)) * 10
fsum<-sum(freq) / f_rows
ss$Freq<-mapply("/",ss$Freq, fsum)
flights<-ss[order(ss$FLIGHT_NAME),]
colnames(flights)<-c("FLIGHT_NAME","FREQUENCY")
lmModel<-lm(FREQUENCY ~ FLIGHT_NAME,data=flights)
modelresult<-data.frame(
ID=c(1),
DESCRIPTION=c("Flight Model"),
MODEL=generateRobjColumn(lmModel)
)
END;
 
--Este procedimiento SQLSCRIPT va a tomar toda la informacion necesaria de las tablas SPFLI y STICKET 
--y la va a asignar a flights
--Vamos a llamar al procedimiento R FLIGHT_TRAIN_PROC
--Vamos a hacer un INSERT para finalmente grabar el buffer en la base de datos
 
DROP PROCEDURE POPULATE_FLIGHTS;
CREATE PROCEDURE POPULATE_FLIGHTS ()
LANGUAGE SQLSCRIPT AS
BEGIN
flights = SELECT SPFLI.CARRID, CUSTOMID, CITYFROM, CITYTO
             FROM SFLIGHT.SPFLI INNER JOIN SFLIGHT.STICKET
             ON SPFLI.CARRID = STICKET.CARRID
             AND SPFLI.CONNID = STICKET.CONNID;
CALL FLIGHT_TRAIN_PROC(:flights, FLIGHT_MODEL_T);
INSERT INTO "FLIGHT_MODEL" SELECT * FROM :FLIGHT_MODEL_T;
END;
 
CALL POPULATE_FLIGHTS();

Cuando llamamos a POPULATE_FLIGHTS(), nuestra tabla FLIGHT_MODEL se deberia ver asi...



Si se preguntan porque tenemos una "X"...es porque el contenido esta serializado y grabado como un campo BLOB...si inspeccionamos el contenido, vamos a recibir varios y raros numeros hexadecimales...

En fin...le tomo 6.165 segundos a SAP HANA procesar 1,842,160 registros.

Ahora que tenemos nuestro modelo guardado de forma segura en la base de datos, podemos movernos hacia nuestro siguiente archivo SQLScript...

Get_and_Use_Flight_Model.sql
--Vamos a crear un TYPE T_PREDICTED_FLIGHTS y una tabla PREDICTED_FLIGHTS para guardar la informacion
--del actual numero de vuelos y el estimado (de acuerdo con nuestra prediccion).
 
DROP TYPE T_PREDICTED_FLIGHTS;
CREATE TYPE T_PREDICTED_FLIGHTS AS TABLE (
CUSTOMID NVARCHAR(8),
FLIGHT_NAME NVARCHAR(60),
FREQUENCY INTEGER,
PREDICTED INTEGER
);
 
DROP TABLE PREDICTED_FLIGHTS;
CREATE TABLE PREDICTED_FLIGHTS (
CUSTOMID NVARCHAR(8),
FLIGHT_NAME NVARCHAR(60),
FREQUENCY INTEGER,
PREDICTED INTEGER
);
 
--En este procedimiento de R, vamos a recibir los vuelos para un cliente en particular, el modelo almacenado 
--en la base de datos y vamos a retornar el resultado para que pueda ser
--guardado en nuestra tabla PREDICTED_FLIGHTS.
--Vamos a utilizar la funcion unserialize() para extraer el modelo.
--Vamos a crear una tabla conteniendo un campo llamado FLIGHT_NAME que sera la concatenacion de los campos
--CARRID, CITYFROM and CITYTO.
--ejm: AA-NEW YORK-SAN FRANCISCO. y tambien el CUSTOMID
--Vamos a convertir la tabla en un data.frame, asi que todos los valores FLIGHT_NAME iguales van a ser
--sumarizados.
--Vamos a utilizar la funcion colnames() para asignar nombres a nuestro data.frame
--Vamos a utilizar la funcion nrow() para obtener el numero de registros en nuestro data.frame (Se guarda en dfrows)
--Vamos a utilizar la funcion rep() para repetir el valor de CUSTOMID del primer registro dfrows veces
--Vamos a utilizar la funcion predict() para predecir la cantidad de vuelos basados en nuestro model (obtenido de 
--la base de datos) y la nueva informacion que hemos obtenido
--Finalmente, vamos a crear un data.frame conteniendo toda la informacion que deberia ser guardada en nuestra
--tabla PREDICTED_FLIGHTS

DROP PROCEDURE USE_FLIGHT;
CREATE PROCEDURE USE_FLIGHT(IN flights T_FLIGHTS, IN modeltbl FLIGHT_MODEL_T, OUT out_flights T_PREDICTED_FLIGHTS)
LANGUAGE RLANG AS
BEGIN
lmModel<-unserialize(modeltbl$MODEL[[1]])
tab<-table(FLIGHT_NAME=paste(flights$CARRID,flights$CITYFROM,flights$CITYTO,sep="-"),CUSTOMID=flights$CUSTOMID)
df<-data.frame(tab)
colnames(df)<-c("FLIGHT_NAME","CUSTOMID","FREQUENCY")
dfrows<-nrow(df)
customid<-rep(df$CUSTOMID[1],dfrows)
prediction=predict(lmModel,df,interval="none")
out_flights<-data.frame(CUSTOMID=customid,FLIGHT_NAME=df$FLIGHT_NAME,FREQUENCY=df$FREQUENCY,PREDICTED=prediction)
END;
 
--Este procedimiento SQLSCRIPT seleccionara informacion de la tabla FLIGHT_MODEL y la guardara en la variable
--flight_model
--Vamos a seleccionar toda la informacion necesaria de las tablas SPFLI y STICKET basada en el customer ID
--Vamos a llamar al procedimiento R USE_FLIGHT y este nos retornara PREDICTED_FLIGHTS que vamos a  
--guardar en la base de datos
 
DROP PROCEDURE GET_FLIGHTS;
CREATE PROCEDURE GET_FLIGHTS(IN customId NVARCHAR(8))
LANGUAGE SQLSCRIPT AS
BEGIN
flight_model = SELECT * FROM FLIGHT_MODEL;
out_flights = SELECT SPFLI.CARRID, CUSTOMID, CITYFROM, CITYTO FROM SFLIGHT.SPFLI INNER JOIN SFLIGHT.STICKET
                   ON SPFLI.CARRID = STICKET.CARRID AND SPFLI.CONNID = STICKET.CONNID
                   WHERE CUSTOMID = :customId;
CALL USE_FLIGHT(:out_flights, :flight_model, PREDICTED_FLIGHTS);
INSERT INTO "PREDICTED_FLIGHTS" SELECT * FROM :PREDICTED_FLIGHTS;
END;

Ahora que tenemos todos nuestros Stored Procedures listos...podemos crear el ultimo archivo SQLScript para finalmente llenar nuestra tabla PREDICTED_FLIGHTS con algunos registros...

Predict_Flights_for_Customers.sql
CALL GET_FLIGHTS('00000156');
CALL GET_FLIGHTS('00002078');
CALL GET_FLIGHTS('00002463');

Como pueden ver...solo necesitamos llamar al procedimiento GET_FLIGHTS, pasando el Customer ID...

Este proceso tomo solamente 970ms y genero 122 registros para los 3 clientes...

Ahora estoy seguro de que se dan cuenta lo impresionante que es esto...si no hubieramos guardado nuestro modelo en la base de datos...tendriamos que calcular el modelo para cada cliente...y nos tomaria aproximadamente 7 segundos para cada uno (obtener el lm(), ademas de la prediccion)...eso seria alrededor de 21 segundos para los 3 clientes...mientras que podemos decir que todo el proceso nos tomo algo de 7 segundos...si tuvieran que calcular la prediccion para todos los mas de un millon de clientes...estarias en un problema serio -:)

Veamos el contenido de nuestra tabla PREDICTED_FLIGHTS...por suepuesto, solo voy a mostrar una parte...


Podemos volcar esta informacion un archivo CSV desde SAP HANA Studio y digamos...utilizarlo en SAP Visual Intelligence para generar un bonito grafico...


Espero que les guste -:)

Saludos,

Blag.

No comments: