En este post explicare, con un ejemplo practico, como convertir claves de otros sistemas en claves subrogadas, y en base a esto generar una dimensión de tales valores.
Esto es especialmente útil cuando las claves del sistema origen son de texto o las claves del sistema origen son claves compuestas, ya que es mucho mas fácil manipular claves numéricas.
Para crear y mantener la clave subrogada, utilizare el objeto “hash”, introducido en la versión 9.1 de SAS, y extendido en la versión 9.2 del mismo.
Ejemplo: Segmento de clientes, originalmente como texto, y queremos subrogarlo para identificarlo por un numero
Esta parte del código es solamente para preparar los datos, en el caso hipotético planteado, el resultado de este código, serian los “Datos de origen”, este código lo pongo para que puedan probar con estos datos de entrada como ejemplo:
data clientes_temp;
attrib customer_id length=8;
attrib customer_segmento_id length=8;
attrib customer_attrib1 length=8;
do customer_id = 1 to 3000000;
customer_segmento_id = int(rand('UNIFORM') * 10) + 1;
customer_attrib1 = int(rand('UNIFORM') * 10000) + 1;
output;
end;
run;
data productos_temp;
attrib customer_segmento_id length=8;
attrib customer_segmento_descripcion length=$30;
infile datalines delimiter=',';
input customer_segmento_id customer_segmento_descripcion $;
datalines;
1,Lider
2,Comun
3,En riesgo
4,Deudor
5,Fraudulento
6,Pasivo
7,Comprador ocasional
8,En camino a riesgo
9,En recuperacion
10,Premium
;
run;
proc sql;
create table clientes as
select
customer_id,
customer_segmento_descripcion,
customer_attrib1
from
clientes_temp c, productos_temp p
where c.customer_segmento_id = p.customer_segmento_id
;
quit; |
Luego de ejecutar el codigo anterior que crea los datos de ejemplo, prueben este codigo que los subroga:
%macro creasinoexiste();
%let dsid=%sysfunc(open(productos, i));
%if dsid ^= 0 %then %do;
data productos;
attrib customer_segmento_id length=8;
attrib customer_segmento_descripcion length=$30;
stop;
run;
%end;
%let dsid=%sysfunc(close(&dsid));
%mend;
%creasinoexiste;
data clientes_segmento_subrogado;
set clientes END=FIN;
if _N_ = 1 then do;
declare hash prod(dataset:'productos');
prod.defineKey('customer_segmento_descripcion');
prod.defineData('customer_segmento_id', 'customer_segmento_descripcion');
prod.defineDone();
end;
if(prod.find(key:customer_segmento_descripcion) ^= 0) then do;
customer_segmento_id=prod.num_items+1;
prod.add(key:customer_segmento_descripcion, data:customer_segmento_id, data:customer_segmento_descripcion);
end;
if FIN then do;
prod.output(dataset:'productos');
end;
run; |
¡Cuanto código!
Expliquemos paso a paso:
%macro creasinoexiste();
%let dsid=%sysfunc(open(productos, i));
%if dsid ^= 0 %then %do;
data productos;
attrib customer_segmento_id length=8;
attrib customer_segmento_descripcion length=$30;
stop;
run;
%end;
%let dsid=%sysfunc(close(&dsid));
%mend;
%creasinoexiste; |
Esta macro es un código sencillo que crea el dataset vacio cuando no lo encuentra, esto es importante ya que luego tratamos de abrir el dataset, y si no lo encuentra el hash tira un error.
Podemos crear un hash sin un dataset, “de la nada”, pero la idea del ejemplo es hacerlo asi ya que si mañana ingresamos mas datos de entradas y los subrogamos no vamos a querer crear las claves desde cero, si antes “Lider” era “3”, queremos que mañana se vuelva a asignar a “3”. (Asumo que es lo mas sano, obviamente depende de la aplicación que le quieran dar).
data clientes_segmento_subrogado;
set clientes END=FIN; |
Aquí definimos que queremos tomar el dataset “Clientes”, guardar una variable de control “FIN” para saber cuando llegamos al final del dataset, y que queremos escribir en “Clientes_segmento_subrogado”, nada fuera de lo comun hasta aqui.
if _N_ = 1 then do;
declare hash prod(dataset:'productos');
prod.defineKey('customer_segmento_descripcion');
prod.defineData('customer_segmento_id', 'customer_segmento_descripcion');
prod.defineDone();
end; |
En este paso indicamos que al encontrarnos en el primer registro (Es decir, cuando recién empezó el datastep) se cree un objeto hash llamado “prod”. La opción dataset:’productos’ del declare hash, le indica que debe cargar los datos ya existentes en el dataset productos.
El método defineKey le indica al hash, cual es la clave por la cual debe buscar, es decir, esta debería ser la clave del sistema origen, en nuestro caso es la “descripción del segmento”.
El método defineKey le indica al hash, cuales son los datos a escribir en el registro al encontrar una clave, y en la dimensión si esta es guardada con el comando output.
if(prod.find(key:customer_segmento_descripcion) ^= 0) then do;
customer_segmento_id=prod.num_items+1;
prod.add(key:customer_segmento_descripcion, data:customer_segmento_id, data:customer_segmento_descripcion);
end; |
Estas sentencias, a diferencia de las anteriores, se aplican a cada registro del dataset clientes, por cada observación, se busca la clave customer_segmento_descripcion en el hash, si se encuentra, el find será igual a 0, y se escribirá el valor correspondiente de customer_segmento_id.
Si el find es distinto a 0, significa que el valor no existía en el hash, en este caso tenemos que inventar una nueva clave subrogada (En mi caso aumento de a 1 el valor según la cantidad de registros del hash), esta nueva clave la agregamos al hash con el comando add, entonces si aparece la misma descripción en este datastep, estaremos asegurándonos de asignarle la misma clave.
if FIN then do;
prod.output(dataset:'productos');
end;
run; |
Esto ultimo es sencillo, estamos guardando los datos contenidos en memoria por el hash, en un dataset de salida.
Creo importante realizar las siguientes aclaraciones:
- En el ejemplo por simplicidad se usa para todo la librería work, sin embargo la dimensión resultante idealmente debería ser un dataset permanente, o una tabla en una base de datos, ya que el subrogado es un proceso de transformación que puede ser difícil o imposible de replicar (En general, no por usar Hash en particular).
- El hash trabaja en memoria RAM, es posible que sea necesario aumentar el memsize y el realmemsize para poder trabajar con este tipo de procesos. Para calcular el máximo debería usarse la formula:RAM necesaria = (Cantidad de Registros máximos HASH) * (Cantidad de bytes sumada de todos los key + Cantidad de bytes sumada de todos los data)
Es decir, debería ser ligeramente mas que lo necesario para guardar un dataset. Recomiendo probar cargar un hash de ejemplo con la cantidad estimada de registros que creen que tendrá como maximo, para evitar problemas con procesos automatizados o recurrentes.
Ademas es importante tener en cuenta cual será la cantidad de RAM disponible en la computadora en donde funcione el proceso.
- Son importantes ventajas del hash, contra otras soluciones para subrogar:
- Su independencia del orden interno del dataset, no hay que hacer un proc sort para hacer un merge, se puede “Mergear desordenado” por asi decirlo.
- Al ser un hash, se tarda muy poco en encontrar la key pedida, incluso si hay millones de registros en el hash.
- No es necesario ningún índice en ninguno de los datasets involucrados, acelerando tanto la creación, y actualización de los datasets, y ahorrando espacio en disco.
Como siempre, si tienen consultas no duden en consultarme por aquí o a por Twitter (@Sicarul)
¡Saludos, y hasta la proxima!