- Published on
Bloque 03 — Procesamiento y Combinación de Datos en SAS
- Authors
- Name
- Cristina Chapoñan Chamorro
- X
- @Cristina_CCC
Procesamiento y Combinación de Datos en SAS
Introducción
En este bloque profundizaremos en las técnicas fundamentales de procesamiento y combinación de datos en SAS. Abordaremos tanto PROC SQL como el DATA step para crear flujos de procesamiento robustos que integren múltiples fuentes de datos. Al final, los participantes comprenderán cómo realizar JOINs complejos, calcular KPIs agregados, combinar datasets usando diferentes estrategias, y construir datamarts integrados que consoliden información de múltiples fuentes. Este conocimiento es esencial para transformar datos dispersos en información analítica consolidada.
1. PROC SQL vs. DATA Step para Combinación de Datos
SAS ofrece dos enfoques principales para combinar y procesar datos: PROC SQL (basado en el estándar SQL) y el DATA step con MERGE/SET. Cada uno tiene ventajas específicas según el contexto y complejidad de la operación.
- PROC SQL para combinaciones: PROC SQL es ideal para combinaciones complejas, agregaciones y filtros avanzados. Permite realizar JOINs entre múltiples tablas con sintaxis estándar SQL, calcular métricas agregadas con GROUP BY, aplicar filtros con HAVING sobre agregados, y crear subconsultas anidadas para lógicas complejas. Las ventajas incluyen: sintaxis familiar para usuarios de bases de datos, operaciones en una sola instrucción (no requiere ordenamiento previo), capacidad de crear tablas temporales dentro de la consulta, y optimización automática de SAS para grandes volúmenes. Es especialmente útil para generar reportes agregados, crear KPIs por grupos, identificar registros huérfanos entre tablas, y realizar filtros complejos con múltiples condiciones.
/* Ejemplo de INNER JOIN con agregación */
proc sql;
create table work.resumen_cliente as
select c.id, c.nombre_completo, c.pais
, count(v.id_venta) as num_transacciones
, sum(v.monto_total) as total_monto
, mean(v.monto_total) as ticket_promedio
from work.clientes c
inner join work.ventas v
on c.id = v.id_cliente
group by c.id, c.nombre_completo, c.pais
having total_monto > 1000
order by total_monto desc;
quit;
- DATA step con MERGE/SET para combinaciones: El DATA step ofrece mayor control granular sobre el procesamiento fila por fila. Permite lógica condicional compleja durante la combinación, uso de variables automáticas FIRST./LAST. para procesamiento por grupos, retención de variables entre iteraciones con RETAIN, y aplicación de transformaciones personalizadas durante la combinación. Es especialmente potente para: procesar grandes volúmenes con optimización de memoria, aplicar lógica compleja que no se puede expresar fácilmente en SQL, realizar combinaciones con condiciones dinámicas, y mantener control total sobre el orden de procesamiento.
/* Ejemplo de MERGE con lógica condicional */
proc sort data=work.clientes; by id; run;
proc sort data=work.agg_ventas; by id; run;
data work.cliente_enriquecido;
merge work.clientes(in=cli) work.agg_ventas(in=ven);
by id;
/* Clasificar según disponibilidad de datos */
if cli and ven then status = 'Cliente Activo';
else if cli and not ven then status = 'Cliente Sin Ventas';
else if not cli and ven then status = 'Venta Huérfana';
/* Solo conservar clientes válidos */
if cli;
run;
- Cuándo usar cada enfoque: Usar PROC SQL cuando se necesiten agregaciones complejas, múltiples JOINs en una operación, subconsultas o filtros sobre agregados (HAVING). Usar DATA step cuando se requiera lógica condicional compleja, procesamiento fila por fila con FIRST./LAST., transformaciones que no se pueden expresar en SQL, o cuando se necesite optimización de memoria para volúmenes muy grandes.
2. Tipos de JOINs en PROC SQL
Los JOINs son fundamentales para combinar información de múltiples tablas. SAS SQL soporta todos los tipos estándar de JOIN, cada uno con propósitos específicos según las necesidades del análisis.
- INNER JOIN - Intersección: El INNER JOIN devuelve solo las filas que tienen coincidencias en ambas tablas. Es útil para análisis que requieren datos completos de ambas fuentes, como calcular métricas solo para clientes que tienen ventas, o crear reportes que necesiten información validada de múltiples tablas.
/* INNER JOIN: Solo clientes con ventas */
proc sql;
create table work.clientes_con_ventas as
select c.id, c.nombre_completo, c.sexo_full
, v.id_venta, v.producto, v.cantidad, v.monto_total, v.fecha_venta_d
from work.clientes_prep as c
inner join work.ventas_prep as v
on c.id = v.id_cliente;
quit;
- LEFT JOIN - Preservar tabla izquierda: El LEFT JOIN conserva todas las filas de la tabla izquierda, completando con missing values cuando no hay coincidencias en la tabla derecha. Es esencial para análisis de cobertura, identificar clientes sin actividad, y crear vistas completas que no pierdan registros principales.
/* LEFT JOIN: Todos los clientes, con o sin ventas */
proc sql;
create table work.clientes_completo as
select c.id, c.nombre_completo, c.sexo_full
, v.id_venta, v.producto, v.cantidad, v.monto_total, v.fecha_venta_d
from work.clientes_prep as c
left join work.ventas_prep as v
on c.id = v.id_cliente;
quit;
- FULL JOIN - Unión completa: El FULL JOIN conserva todas las filas de ambas tablas, útil para auditorías de integridad referencial, identificar registros huérfanos en cualquier dirección, y análisis de calidad de datos. Requiere usar COALESCE para consolidar claves que puedan estar en cualquier tabla.
/* FULL JOIN: Detectar registros huérfanos en cualquier dirección */
proc sql;
create table work.auditoria_completa as
select coalesce(c.id, v.id_cliente) as id_master length=20
, c.id as id_en_clientes
, v.id_cliente as id_en_ventas
, c.nombre_completo, v.id_venta
from work.clientes_prep c
full join work.ventas_prep v
on c.id = v.id_cliente;
quit;
/* Identificar casos problemáticos */
proc sql;
/* Ventas sin cliente correspondiente */
create table work.ventas_huerfanas as
select * from work.auditoria_completa
where id_en_clientes is null and id_en_ventas is not null;
/* Clientes sin ventas */
create table work.clientes_sin_actividad as
select * from work.auditoria_completa
where id_en_ventas is null and id_en_clientes is not null;
quit;
- Consideraciones de rendimiento: Para datasets grandes, asegúrese de que las variables de unión estén indexadas, use WHERE en lugar de HAVING cuando sea posible para filtrar antes de agregaciones, y considere crear tablas intermedias para consultas muy complejas.
3. Agregaciones y KPIs con GROUP BY
Las agregaciones son fundamentales para generar indicadores de negocio. PROC SQL permite calcular métricas por grupos usando GROUP BY junto con funciones agregadas como SUM, COUNT, MEAN, MIN, MAX.
- KPIs básicos por cliente: El patrón más común es calcular métricas de resumen por cliente, consolidando todas sus transacciones en indicadores de alto nivel.
/* KPIs fundamentales por cliente */
proc sql;
create table work.kpi_cliente as
select c.id
, c.nombre_completo
, count(v.id_venta) as n_transacciones
, sum(coalesce(v.monto_total,0)) as total_monto
, mean(v.monto_total) as ticket_promedio
, min(v.fecha_venta_d) as primera_compra format=date9.
, max(v.fecha_venta_d) as ultima_compra format=date9.
from work.clientes_prep c
left join work.ventas_prep v
on c.id = v.id_cliente
group by c.id, c.nombre_completo
order by calculated total_monto desc;
quit;
- Filtros con HAVING: HAVING permite filtrar grupos después de aplicar las agregaciones, a diferencia de WHERE que filtra registros individuales antes de agrupar. Es esencial para identificar segmentos específicos basados en métricas calculadas.
/* Clientes VIP: más de $3,000 en compras totales */
%let umbral_vip = 3000;
proc sql;
create table work.clientes_vip as
select * from work.kpi_cliente
having total_monto >= &umbral_vip
and n_transacciones >= 3;
quit;
- KPIs por múltiples dimensiones: Es posible crear agregaciones más complejas combinando datos de múltiples tablas y agrupando por diferentes dimensiones.
/* Adjuntar información de vendedor a las ventas */
proc sql;
create table work.ventas_con_vendedor as
select v.*
, c.vendedor
from work.ventas_prep v
left join work.clientes_prep c
on v.id_cliente = c.id;
quit;
/* KPIs por producto */
proc sql;
create table work.kpi_producto as
select producto
, count(*) as n_ventas
, sum(monto_total) as monto_total
, mean(monto_total) as ticket_promedio
, count(distinct id_cliente) as clientes_unicos
from work.ventas_prep
group by producto
order by monto_total desc;
quit;
/* KPIs por vendedor */
proc sql;
create table work.kpi_vendedor as
select vendedor
, count(*) as n_ventas
, sum(monto_total) as monto_total
, mean(monto_total) as ticket_promedio
, count(distinct id_cliente) as clientes_atendidos
from work.ventas_con_vendedor
where vendedor is not missing
group by vendedor
order by monto_total desc;
quit;
4. CASE WHEN y Variables Calculadas
Las expresiones CASE WHEN permiten crear lógica condicional compleja dentro de consultas SQL, mientras que la palabra clave CALCULATED permite reutilizar columnas calculadas en la misma consulta.
- Segmentación con CASE WHEN: Una aplicación común es crear segmentos de clientes basados en sus métricas de comportamiento.
/* Segmentación de clientes por nivel de gasto */
proc sql;
create table work.segmento_cliente as
select k.*
, case
when total_monto >= 5000 then 'VIP'
when total_monto >= 2000 then 'Premium'
when total_monto > 0 then 'Regular'
else 'Sin Compras'
end as segmento length=12
, case
when n_transacciones >= 10 then 'Muy Activo'
when n_transacciones >= 5 then 'Activo'
when n_transacciones >= 1 then 'Ocasional'
else 'Inactivo'
end as actividad length=12
from work.kpi_cliente as k;
quit;
- Uso de CALCULATED: CALCULATED permite referenciar columnas creadas en la misma consulta, especialmente útil en cláusulas HAVING o WHERE.
/* Filtrar por ticket promedio calculado */
proc sql;
create table work.alto_ticket as
select id, nombre_completo
, n_transacciones
, total_monto
, case when n_transacciones > 0
then total_monto/n_transacciones
else . end as ticket_promedio
from work.kpi_cliente
having calculated ticket_promedio > 300;
quit;
5. DATA Step - SET y MERGE
El DATA step ofrece dos instrucciones principales para combinar datasets: SET para concatenación (unir filas) y MERGE para combinación por clave (unir columnas).
- SET - Concatenación vertical: SET apila datasets uno tras otro, útil para consolidar datos de diferentes períodos o fuentes con estructura similar.
/* Separar ventas por año */
proc sql;
create table work.ventas_2023 as
select * from work.ventas_prep
where year(fecha_venta_d) = 2023;
create table work.ventas_2024 as
select * from work.ventas_prep
where year(fecha_venta_d) = 2024;
quit;
/* Concatenar años usando SET */
data work.ventas_consolidadas;
set work.ventas_2023 work.ventas_2024;
/* Se pueden agregar transformaciones adicionales aquí */
run;
- MERGE - Combinación horizontal: MERGE combina datasets por una clave común, similar a JOINs pero con mayor control sobre el procesamiento. Requiere que los datasets estén ordenados por la variable de combinación.
/* Preparar dataset agregado por cliente */
proc sql;
create table work.metricas_cliente as
select id_cliente as id
, count(*) as num_compras
, sum(monto_total) as total_gastado
from work.ventas_prep
group by id_cliente;
quit;
/* Ordenar ambos datasets por la clave */
proc sort data=work.clientes_prep; by id; run;
proc sort data=work.metricas_cliente; by id; run;
/* MERGE con indicadores IN= */
data work.cliente_completo;
merge work.clientes_prep(in=tiene_datos_cliente)
work.metricas_cliente(in=tiene_ventas);
by id;
/* Crear indicadores de disponibilidad */
if tiene_datos_cliente and tiene_ventas then tipo = 'Cliente Activo';
else if tiene_datos_cliente and not tiene_ventas then tipo = 'Cliente Sin Ventas';
else if not tiene_datos_cliente and tiene_ventas then tipo = 'Venta Huérfana';
/* Imputar valores faltantes */
if missing(num_compras) then num_compras = 0;
if missing(total_gastado) then total_gastado = 0;
/* Filtrar según necesidades */
if tiene_datos_cliente; /* Solo conservar clientes válidos */
run;
6. Variables Automáticas FIRST. y LAST.
Cuando se usa BY en un DATA step, SAS crea automáticamente variables especiales FIRST.variable y LAST.variable que indican el primer y último registro de cada grupo.
- Identificar primera y última ocurrencia: Estas variables son especialmente útiles para calcular métricas por grupo o filtrar registros específicos dentro de cada grupo.
/* Encontrar última compra por cliente */
proc sort data=work.ventas_prep out=work.ventas_ordenadas;
by id_cliente fecha_venta_d;
run;
data work.ultima_compra;
set work.ventas_ordenadas;
by id_cliente fecha_venta_d;
/* Conservar información de la última compra */
retain fecha_ultima_compra monto_ultima_compra;
if first.id_cliente then do;
fecha_ultima_compra = .;
monto_ultima_compra = .;
end;
/* Actualizar con cada registro del cliente */
fecha_ultima_compra = fecha_venta_d;
monto_ultima_compra = monto_total;
/* Solo generar salida en el último registro del cliente */
if last.id_cliente then output;
format fecha_ultima_compra date9.;
keep id_cliente fecha_ultima_compra monto_ultima_compra;
run;
- Clasificar clientes por frecuencia: Las variables FIRST./LAST. permiten clasificar fácilmente registros según patrones dentro de cada grupo.
/* Identificar clientes con una sola compra vs múltiples */
data work.clasificacion_frecuencia
work.cliente_unica_compra
work.cliente_multiple_compra;
set work.ventas_ordenadas;
by id_cliente;
if first.id_cliente and last.id_cliente then do;
/* Cliente con una sola compra */
tipo_cliente = 'Compra Única';
output work.cliente_unica_compra;
end;
else if last.id_cliente then do;
/* Cliente con múltiples compras */
tipo_cliente = 'Múltiples Compras';
output work.cliente_multiple_compra;
end;
/* Todas las clasificaciones */
if last.id_cliente then output work.clasificacion_frecuencia;
run;
7. Gestión de Duplicados con PROC SORT
PROC SORT no solo ordena datos, sino que también puede eliminar duplicados usando la opción NODUPKEY.
- Eliminar duplicados por clave: Útil para limpiar datasets que pueden tener registros duplicados por error.
/* Eliminar posibles duplicados de clientes por ID */
proc sort data=work.clientes_prep
out=work.clientes_unicos
nodupkey;
by id; /* Mantiene el primer registro para cada ID */
run;
/* Verificar cuántos duplicados se eliminaron */
proc sql;
select count(*) as registros_originales from work.clientes_prep;
select count(*) as registros_unicos from work.clientes_unicos;
quit;
- Ordenamiento complejo: PROC SORT permite ordenamientos sofisticados con múltiples variables y direcciones.
/* Ordenar ventas por cliente y fecha descendente */
proc sort data=work.ventas_prep
out=work.ventas_por_cliente_desc;
by id_cliente descending fecha_venta_d;
run;
8. Transformación de Datos con PROC TRANSPOSE
PROC TRANSPOSE permite cambiar la estructura de los datos de formato largo a ancho o viceversa.
- De largo a ancho: Convertir múltiples filas por entidad en múltiples columnas.
/* Crear resumen de ventas por cliente y mes */
proc sql;
create table work.ventas_cliente_mes as
select id_cliente
, intnx('month', fecha_venta_d, 0, 'beginning') as mes_inicio format=yymmn6.
, sum(monto_total) as total_mes
from work.ventas_prep
group by id_cliente, calculated mes_inicio;
quit;
/* Ordenar para TRANSPOSE */
proc sort data=work.ventas_cliente_mes;
by id_cliente mes_inicio;
run;
/* Transponer: una columna por mes */
proc transpose data=work.ventas_cliente_mes
out=work.ventas_mes_columnas
prefix=mes_;
by id_cliente;
id mes_inicio;
var total_mes;
run;
9. Funciones de Fecha y Texto
SAS proporciona funciones especializadas para manipular fechas y texto que son esenciales en el procesamiento de datos.
- Funciones de fecha INTCK e INTNX: INTCK calcula intervalos entre fechas, INTNX calcula fechas futuras o pasadas.
/* Calcular edad de clientes y días desde última compra */
proc sql;
create table work.analisis_temporal as
select c.id, c.nombre_completo, c.fecha_nac
, u.fecha_ultima_compra
, intck('year', c.fecha_nac, today()) as edad_anos
, intck('day', u.fecha_ultima_compra, today()) as dias_desde_ultima
, intnx('month', u.fecha_ultima_compra, 3, 'same') as proxima_campana format=date9.
from work.clientes_prep c
left join work.ultima_compra u
on c.id = u.id_cliente
where c.fecha_nac is not missing;
quit;
- Funciones de texto: Permiten manipular y extraer información de campos de texto.
/* Crear códigos de cliente y extraer dominios de email */
data work.clientes_enriquecidos;
set work.clientes_prep;
length codigo_cliente $15 dominio_email $50;
/* Código compuesto: país + ID formateado */
codigo_cliente = catx('-', upcase(pais), put(input(id, best8.), z8.));
/* Extraer dominio de email */
if not missing(email) and index(email, '@') > 0 then do;
dominio_email = substr(email, index(email, '@') + 1);
end;
else dominio_email = 'Sin Email Válido';
run;
10. Construcción de DataMart Integrado
Un DataMart consolida información de múltiples fuentes en una vista analítica integrada.
/* Paso 1: KPIs completos por cliente */
proc sql;
create table work.kpis_finales as
select k.*
, case when n_transacciones > 0
then total_monto/n_transacciones
else . end as ticket_promedio
from work.kpi_cliente k;
quit;
/* Paso 2: Información del vendedor */
proc sql;
create table work.info_vendedor as
select c.id
, c.vendedor
, v.region
, input(v.fecha_ingreso, ddmmyy10.) as fecha_ingreso_vendedor format=date9.
, v.objetivo_mensual
from work.clientes_prep c
left join work.vendedores v
on c.vendedor = v.vendedor;
quit;
/* Paso 3: DataMart final integrado */
proc sql;
create table work.datamart_clientes as
select c.id, c.nombre_completo, c.sexo_full, c.pais, c.email
/* KPIs de comportamiento */
, k.n_transacciones, k.total_monto, k.ticket_promedio
, k.primera_compra, k.ultima_compra
/* Información temporal */
, u.fecha_ultima_compra, u.monto_ultima_compra
, intck('day', u.fecha_ultima_compra, today()) as dias_inactividad
/* Información del vendedor */
, v.vendedor, v.region, v.fecha_ingreso_vendedor, v.objetivo_mensual
/* Segmentación */
, case
when k.total_monto >= 5000 then 'VIP'
when k.total_monto >= 2000 then 'Premium'
when k.total_monto > 0 then 'Regular'
else 'Sin Compras'
end as segmento length=12
/* Recencia */
, case
when calculated dias_inactividad <= 30 then 'Reciente'
when calculated dias_inactividad <= 90 then 'Moderado'
when calculated dias_inactividad <= 180 then 'En Riesgo'
else 'Inactivo'
end as recencia length=12
from work.clientes_prep c
left join work.kpis_finales k on c.id = k.id
left join work.ultima_compra u on c.id = u.id_cliente
left join work.info_vendedor v on c.id = v.id
order by k.total_monto desc;
quit;
11. Validaciones de Calidad de Datos
Es fundamental implementar controles de calidad para identificar problemas en los datos integrados.
/* Control 1: Ventas sin cliente correspondiente */
proc sql;
create table work.control_ventas_huerfanas as
select v.*
from work.ventas_prep v
left join work.clientes_prep c
on v.id_cliente = c.id
where c.id is null;
/* Contar registros problemáticos */
select count(*) as ventas_sin_cliente
from work.control_ventas_huerfanas;
quit;
/* Control 2: Emails inválidos */
data work.control_emails;
set work.clientes_prep;
length problema_email $30;
if missing(email) then problema_email = 'Email Faltante';
else if index(email, '@') = 0 then problema_email = 'Sin Arroba';
else if length(scan(email, 2, '@')) = 0 then problema_email = 'Dominio Vacío';
else problema_email = 'OK';
if problema_email ne 'OK';
run;
/* Control 3: Duplicados potenciales */
proc sql;
create table work.control_duplicados as
select nombre_completo, email, count(*) as n_duplicados
from work.clientes_prep
where not missing(nombre_completo) and not missing(email)
group by nombre_completo, email
having n_duplicados > 1;
quit;
Recursos Adicionales
Referencias recomendadas — Procesamiento y combinación de datos en SAS
PROC SQL User's Guide (SAS Help Center) — Documentación completa de PROC SQL con ejemplos de JOINs, agregaciones y subconsultas.
Enlace: https://documentation.sas.com/doc/en/proc/9.4/n1qbkscrs8c6zqn1a0ajxs3yn7ha.htmDATA Step Processing (SAS Help Center) — Guía detallada del DATA step, incluyendo MERGE, SET y variables automáticas FIRST./LAST.
Enlace: https://documentation.sas.com/doc/en/basess/9.4/p0s9hgavnajsgsn1x4rkqzawjpz7.htmWorking with Time Series Data in SAS — Tutorial especializado en funciones de fecha y manipulación temporal.
Enlace: https://blogs.sas.com/content/sgf/2019/01/25/working-with-time-series-data/SAS Communities — Data Integration Forum — Foro activo con preguntas y soluciones sobre combinación de datos.
Enlace: https://communities.sas.com/t5/SAS-Data-Management/bd-p/data_managementEfficient Data Processing in SAS (Rick Wicklin) — Blog con técnicas avanzadas de optimización para procesamiento de grandes volúmenes.
Enlace: https://blogs.sas.com/content/iml/2018/05/30/efficient-data-processing-sas.html