SAS: Cálculo de la edad

 

Se puede calcular la edad de muchas maneras, pero la más cómoda es utilizando la función intck(). Esta función calcula distintas entre fechas expresadas en distintas unidades de medidas: días, meses, años, etc. Ya he escrito algo sobre esta función en este artículo.

Para calcular la edad natural a fecha de hoy podemos utilizar la siguiente fórmula, que es muy simple:

edad = intck('year',fec_nacimiento,date(),'c');

En la función anterior, ‘year’ indica que se va a medir la diferencia entre las fechas en años, el segundo parámetro es la fecha de nacimiento que tengamos. En el tercero indicamos la fecha de hoy con la función date() y finalmente, el cuarto parámetro indica que la diferencia en años resultante tiene que ser a años cumplidos, o sea, que tener 35 años y 11 meses implica que la función devolverá 35 años.

Si queremos calcular esa edad a una fecha concreta del pasado, o del futuro, solo debemos sustituir el segundo parámetro por la fecha que queramos, por ejemplo ’31dec2022’d:

edad = intck('year',fec_nacimiento,'31dec2022'd,'c');

Finalmente, hay otra edad que se calcula en el mundo de los seguros que es la edad técnica y que sirve para calcular el riesgo asociado a un individuo y su prima correspondiente. Esta edad técnica es el redondeo de la edad natural al entero más cercano, o sea, 35 años y 7 meses implica que tienes 36 años técnicos. Esto se calcularía así:

edad_tecnica = intck('year',fec_nacimiento,date());
edad_tecnica2 = round((date() - fec_nacimiento) / 365.25);

Entre estas dos últimas hay una pequeña diferencia cuando te aproximas a la fecha de cumpleaños, pero he visto la segunda muchas veces en muchos ejemplos de código. Yo, prefiero la primera, que es más exacta.

SAS Guide: Ejecutar código al conectarse

En ocasiones utilizamos repetidas veces las mismas definiciones de librerías que no están predeterminadas por el administrador en el sistema o tenemos que cargar ciertas macros que utilizamos repetidamente en nuestros distintos programas. Este trabajo de repetir estos trozos de código a veces puede resultar pesado e incluso comerse parte de nuestro preciado tiempo.

Una forma para realizar esta tarea de forma automática cada vez que se inicia SAS Guide es utilizando el autoexec. Este es un fichero que contiene un trozo de código que se ejecutará al inicio de la sesión y que es exclusivo de cada usuario, por lo que podemos personalizarlo a nuestro gusto.

Podemos editar este autoexec desde las opciones de SAS Guide. Para ello, primero crearemos el programa que contiene el código que necesitamos ejecutar y lo guardaremos preferiblemente en una ruta de red donde otros usuarios puedan alcanzarlo para que puedan utilizar nuestro código. No es nada recomendable guardarlo en una carpeta personal si se trabaja en un equipo.

Una vez hecho esto, en el menú de SAS Guide vamos al menú Herraminetas > Opciones > Programas SAS y hacemos check en la opción «Procesar el código SAS cuando el servidor esté conectado». Esto ejecutará el siguiente código cuando se realice una conexión al workspace. De esta forma, las librerías o macros que creemos estarán disponibles nada más abrir SAS y podrán ser llamadas desde cualquier programa que ejecutemos nosotros.

Menú de Opciones de SAS Guide

Para editar el código que se ejecutará en ese momento haremos clic en el botón Editar. Aparecerá una ventana de código donde incluiremos la llamada al código que nos interese. En este caso recomiendo incluir un include que llame al programa SAS que ya habíamos generado. Para ello, escribimos:

%include «[ruta]\[nombre_programa.sas]»;

Donde ruta es la ruta completa de la carpeta donde se encuentra el código y nombre_programa.sas el nombre del programa que se ha creado.

Cualquier otro usuario que ejecute un programa que contenga estas librerías o macros que hemos definido aquí; o en caso de planificar un programa, no los tendrán disponibles por defecto, ya que este cambio solo afecta a tu propia sesión. Pero esto se arregla fácilmente incluyendo la misma línea del %include que antes hemos visto al principio de ese programa y todo funcionará correctamente.

SAS: Operar con fechas: función intck()

La función intck(), al igual que intnx(), es una de las más utilizadas para trabajar con fechas. intck() calcula distancias entre dos fechas que se le proporcionan. Los usos más comunes son calcular la edad, calcular la antigüedad de un cliente o contrato, calcular la añada, etc. Veamos su sintaxis básica:

Sintaxis

intck(intervalo, fecha_inicio; fecha_fin)

Esta función calcula, como hemos dicho, la distancia entre fecha_inicio y fecha_fin expresándola en las unidades de un intervalo concreto. Esto es, se puede medir esa distancia en años, en meses o en días si fuera necesario. Vamos a ver algunos ejemplos:

intck(‘day’,’1jul2021’d,’1dec2021’d) 153 Restar días es equivalente a hacer la operación ‘1dec2021’d – ‘1jul2021’d, ya que las fechas se pueden operar directamente.
intck(‘month’,’1jul2021’d,’1dec2021’d) 5 Transcurren 5 meses entre las dos fechas: julio, agosto, septiembre, octubre y noviembre.
intck(‘month’,’31jul2021’d,’1dec2021’d) 5 Sin embargo, en este caso también se contabilizan 5 meses a pesar de que se está contando desde el último día de julio.
intck(‘year’,’31dec2020’d,’1jan2021’d) 1 Lo mismo ocurre contando años desde el último día de un año y el primero del siguiente.

Método

El método es el cuarto (y último) parámetro que podemos incluir en intck. Indica como vamos a tratar la operación de cálculo de la distancia entre fechas, si vamos a utilizar el método continuo o discreto.

Método continuo

El método continuo considera que es necesario que se cumpla un periodo completo para contarlo, esto quiere decir, si estamos midiendo una distancia en meses tienen que haber pasado todos los días de un mes hasta llegar al mismo día de mes del mes siguiente para que cuente 1. Si falta un solo día el resultado de la operación será cero. Este es el tipo de operación que debemos utilizar para calcular la edad y debemos indicarlo con una ‘C’ como cuarto parámetro.
Vamos a verlo con un ejemplo:

intck(‘year’,’31dec2020’d,’1jan2021’d,’C’) 0
intck(‘year’,’31dec2020’d,’31dec2021’d,’C’) 1

Método discreto

El método discreto contabiliza cuantos finales de periodo han ocurrido entre ambas fechas. Este es el valor por defecto del método y se indica con una ‘D’ como último parámetro. De esta manera, tomando el ejemplo anterior, la distancia en años entre el 31 de diciembre y el 1 de enero del año siguiente es 1. Este método es apropiado para calcular, por ejemplo, cuántas renovaciones ha tenido una póliza de seguros.

intck(‘year’,’31dec2020’d,’1jan2021’d,’D’) 1
intck(‘year’,’31dec2020’d,’31dec2021’d,’D’) 2

SAS: Convertir cadenas de texto a número o fecha

Hay varias operaciones que hay que hacer frecuentemente para convertir cadenas de texto a otros tipos de formato o a la inversa.

Empezaremos con lo más simple. Por ejemplo, para convertir un número a texto debemos de utilizar la función put() que admite como parámetro un número y devuelve el formato indicado (también se puede utilizar para dar formatos de fecha):

data salida;
    b = 1;
    b1 = put(b,$2.);
run;

Resultado de conversión número a texto
Y para la operación contraria, convertir una cadena en número, utilizamos la función input() que hace la operación inversa:

data salida;
    a = '1';
    a1 = input(a,8.);
run;

Resultado de conversión texto a número
Convertir una cadena de texto con una fecha en un campo fecha es un poco más complicado. Necesitamos tomar cada valor de mes, día y año de esa cadena e introducirlos como parámetros de la función mdy(). Pero mdy() solo admite números como parámetros por lo que debemos convertirlos con un input() previamente. Esto quedaría así:

data salida;
    f = '01/12/2020';
    mes = input(substr(f,4,2),8.);
    dia = input(substr(f,1,2),8.);
    anyo = input(substr(f,7,4),8.);
    format fecha ddmmyys10.;
    fecha = mdy(mes,dia,anyo);
run;

Convertir texto a fecha
De forma más directa y resumida podríamos expresarlo de la siguiente manera:

data salida;
    f = '25/12/2020';
    format fecha ddmmyys10.;
    fecha = mdy(input(substr(f,4,2),8.),input(substr(f,1,2),8.),input(substr(f,7,4),8.));
run;

Convertir texto a fecha
Únicamente debemos tener en cuenta con este método qué va a pasar con las fechas en los que el día y el mes vengan expresados por un solo dígito: ¿vendrán como 01 o como 1? Para resolver este punto podemos cambiar nuestro substr() por un scan(). Esta función divide una cadena por el carácter que se le indique y nos devuelve la subcadena que ocupe la posición indicada por parámetro. Para el caso anterior, lo expresaríamos así:

data salida;
    f = '01/12/2020';
    mes = input(scan(f,2,'/'),8.);
    dia = input(scan(f,1,'/'),8.);
    anyo = input(scan(f,3,'/'),8.);
    format fecha ddmmyys10.;
    fecha = mdy(mes,dia,anyo);
run;

Convertir texto a fecha
Convertir una fecha en una cadena de texto se consigue por la combinación de las funciones put() e input(). El put() que va en el interior sirve para poner la fecha en el formato adecuado antes de convertirlo en texto. En caso contrario la fecha se convertirá como el número que es, en texto. Una vez tenemos el formato adecuado lo convertimos a texto con un input().

data salida;
    fecha = '25dec2020'd;
    fechado = input(put(fecha,yymmddn8.),$8.);
run;

Convertir fecha a texto

Coordenada dentro de un polígono

Si alguna vez os encontráis con el problema a resolver de saber si una coordenada, una dirección, una población, etc. se encuentra dentro de un polígono o de una área cualquiera que tienes definida por un listado de coordenadas esta pequeña macro hecha con SAS puede ayudarte.


¿Se encuentran estos puntos fuera o dentro del polígono?

Tenemos para este ejercicio los siguientes datos. Una tabla que contiene el polígono y otra que contiene el listado de coordenadas a evaluar.

data POLIGONO;
    poligono="40.52,-4.32 40.55,-4.32 40.56,-4.29 40.53,-4.24 40.52,-4.26 40.52,-4.32";
run;
 
data COORDENADAS;
    input coor_x coor_y;
    datalines;
40.54548 -4.29331 /*dentro*/
40.56126 -4.34155 /*fuera*/
40.54 -4.32 /*se encuentra en el borde de una línea horizontal*/
40.52 -4.30 /*se encuentra en el borde de una línea vertical*/
40.49994 -4.28138 /*fuera*/
40.70045 -3.91955 /*fuera*/
40.52 -4.32 /*vértice*/
;
run;

Hay varias maneras de determinar si un punto se encuentra dentro o fuera de un polígono, utilizaremos una válida tanto para polígonos cóncavos como convexos, basada en el teorema de la curva de Jordan. De esta manera, si trazamos una línea cualquiera a partir de nuestro punto a evaluar, esta cortará el polígono un número impar de veces si el punto se encuentra dentro del mismo o 0 o un número par en caso contrario.

Lo primero que haremos será transponer el contenido del polígono que está representado en el formato siguiente (las coordenadas separados por coma y cada dupla separada por un espacio):

40.52,-4.32 40.55,-4.32 40.56,-4.29 40.53,-4.24 40.51,-4.26 40.52,-4.32

Para ello calculamos el número de vértices calculando el número de espacios con un compress y luego transponemos el contenido de forma que la salida es una tabla en la que cada registro contiene un segmento del polígono.

Así nuestro cálculo consistirá en saber si una línea horizontal trazada a partir de nuestro punto cruza una o varias veces. Utilizamos la siguiente macro:.

%macro poligono;
    data POLIGONO;
        set POLIGONO;
        num_vertices = length(poligono) - length(compress(poligono));
        do x=1 to num_vertices;
            origen_x = input(scan(poligono,(x*2)-1,' ,'),8.2);
            origen_y = input(scan(poligono,(x*2),' ,'),8.2);
            destino_x = input(scan(poligono,((x+1)*2)-1,' ,'),8.2);
            destino_y = input(scan(poligono,((x+1)*2),' ,'),8.2);
            output;
        end;
    run;

    %let dsid = %sysfunc(open(POLIGONO));
    data SALIDA (drop=xinters);
        retain id_alerta;
        set COORDENADAS;
        retain borde interseccion;

        %let nobs = %sysfunc(attrn(&dsid,NOBS));
        %let rc = %sysfunc(fetchobs(&dsid,1));
        %do i=1 %to &nobs;
            %let num_vertices = %sysfunc(getvarn(&dsid,%sysfunc(varnum(&dsid,num_vertices))));
            %let x = %sysfunc(getvarn(&dsid,%sysfunc(varnum(&dsid,x))));
            %let origen_x = %sysfunc(getvarn(&dsid,%sysfunc(varnum(&dsid,origen_x))));
            %let origen_y = %sysfunc(getvarn(&dsid,%sysfunc(varnum(&dsid,origen_y))));
            %let destino_x = %sysfunc(getvarn(&dsid,%sysfunc(varnum(&dsid,destino_x))));
            %let destino_y = %sysfunc(getvarn(&dsid,%sysfunc(varnum(&dsid,destino_y))));

            %let rc = %sysfunc(fetch(&dsid));
            if &x=1 then do;
                borde=0;
                interseccion=0;
            end;

            if coor_x = &origen_x and coor_y = &origen_y then do;
                resultado='VERTICE';
                output;
            end;
            else if not(coor_x = &destino_x and coor_y = &destino_y) then do;
                if &origen_y = &destino_y and 
                    &origen_y = coor_y and 
                    coor_x > min(&origen_x,&destino_x) and 
                    coor_y <= min(&origen_y,&destino_y) and
                    coor_y <= max(&origen_y,&destino_y) and
                    coor_x <= max(&origen_x,&destino_x) and
                    &origen_y ne &destino_y then do;
                        xinters = (coor_y - &origen_y) * (&destino_x - &origen_x) / (&destino_y - &origen_y) + &origen_x;

                    if xinters = coor_x then borde=1;
                    if (&origen_x = &destino_x or coor_x <= xinters) then interseccion=interseccion+1;
                end;

                if &x = &num_vertices then do;
                    if mod(interseccion,2)=1 or borde=1 then do;
                        resultado='DENTRO';
                        output;
                    end;
                    else do;
                        resultado='FUERA';
                        borde=0;
                        interseccion=0;
                        output;
                    end;
                end;
            end;
        %end;
    run;
    %let rc = %sysfunc(close(&dsid));
%mend;
%poligono;

 
Y el resultado que obtenemos es el siguiente:

Esta macro tiene la particularidad de que identifica correctamente cuando un punto se encuentra en un borde, pero en esos casos el indicador interseccion indicaría que se encuentran fuera del área del polígono. Hay que tener en cuenta esto y considerarlo en función de nuestros intereses. Yo, aquí, los estoy asumiendo como dentro.

 

Referencias: 

Basado en el código de AssemblySys dataServices: https://assemblysys.com/es/algoritmo-punto-en-poligono

Convertir de número a fecha cuando hay ceros

En algunas ocasiones nos encontramos que en una tabla los campos de fecha aparecen en numérico y llevan los valores no informados rellenados con un 0. O sea, hay un 0 en vez de un nulo. En este artículo quiero ofreceros algunas opciones para convertir rápidamente estos números en un formato fecha de SAS tradicional, de forma que podamos operar normalmente con ellos. Veremos ejemplos cuando el campo de origen es de texto, o es numérico.

En primer lugar, una tabla para los ejemplos que van abajo:

data EJEMPLO;
fecha_inicio = 20220101;
fecha_fin = 20220331;
output;
fecha_inicio = 20220101;
fecha_fin = 0;
run;

Formato número YYYYMMDD a fecha


data FECHAS1;
    set EJEMPLO;
    format fc_inicio ddmmyyn8.;
    if fecha_inicio = 0 then fc_inicio = .;
    else fc_inicio = mdy(mod(floor(fecha_inicio/100),100),mod(fecha_inicio,100),floor(fecha_inicio/10000));
run;

Formato número DDMMYYYY a fecha


data FECHAS2;
    set EJEMPLO;
    format fc_fin ddmmyys10.;
    if fecha_fin = 0 then fc_fin = .;
    else fc_fin = mdy(mod(floor(fecha_fin/10000),100),floor(fecha_fin/1000000),mod(fecha_fin,10000));
run;

Convertir mes YYYYMM a fecha

A veces tienes un dato de fecha expresado como mes. El problema para crear una fecha a partir de él es que no conocemos el día, así que hay que asumir que nos valdría o el primer o el último día del mes. La forma de convertirlo es así:


data FECHAS3;
    mes = 202208;
    format fc_mes_1 fc_mes_2 ddmmyys10.;
    /* primer día del mes */
    fc_mes_1 = mdy(mod(mes,100),1,floor(mes/100));
    /* último día del mes */
    fc_mes_2 = intnx('month',mdy(mod(mes,100),1,floor(mes/100)),0,'e');
run;

Para saber algo más de los tipos de formato de fecha que puedes utilizar visita este link.

Google Sheets: Convertir bitcoins a euros

Hay unas 10.000 criptomonedas, pero se puede conocer la cotización de las principales con Google Sheets.
Para ello debemos utilizar la función GOOGLEFINANCE. Esta función permite convertir la cotización de varios tipos de activos financieros (acciones, criptomonedas, etc.) a varias divisas con una actualización frecuente.

Su sintaxis general es la siguiente:
GOOGLEFINANCE(ticker, [attribute], [start_date], [end_date|num_days], [interval])
El «ticker» es el tipo y nombre de divisa, acción o criptomoneda de la que se quiere conocer su valor. Ese parámetro va entrecomillado y se indica con el nombre del índice bursátil y el del ticker separados por dos puntos. Se puede buscar el nombre de los índices bursátiles y los tickers disponibles en esta web.

En el caso de las criptomonedas se usa "Currency:AAAXXX". AAA será la divisa (euro, dólar, etc.) o la criptomoneda desde la que queramos convertir y XXX será la de destino. Pongo algunos ejemplos:

=GOOGLEFINANCE("Currency:BTCEUR") Para convertir de Bitcoins a euros. Multiplicado este valor por el número de bitcoins que tienes, te dará su valor actual.
=GOOGLEFINANCE("Currency:BTCUSD") Para convertir de Bitcoins a dólares.
=GOOGLEFINANCE("Currency:ETHEUR") Para convertir de Etherium a euros.
=GOOGLEFINANCE("Currency:EURETH") Para convertir de euros a Etherium.
=GOOGLEFINANCE("Currency:ADAEUR") Para convertir de Etherium a euros.
=GOOGLEFINANCE("Currency:BTCMXN") Para convertir de Bitcoins a pesos mexicanos.
=GOOGLEFINANCE("Currency:ETHPEN") Para convertir de Etherium a soles peruanos.

Existe un listado de ejemplos con todas las criptomonedas disponibles aquí: https://www.google.com/finance/markets/cryptocurrencies?hl=es

En el siguiente enlace de Google tienes la descripción completa de la función: https://support.google.com/docs/answer/3093281?hl=es

SAS: Convertir de texto a fecha

Convertir de un texto a una fecha es una pregunta habitual para nuevos programadores en SAS, hay varios enfoques válidos para hacerlo:
La mejor solución al problema es aplicar la función input() sobre la cadena de entrada para cambiarle el informat al nuevo campo (fecha) e inmediatamente después aplicar el formato con put(). Incluyo dos ejemplos distintos de formato de entrada y de salida:

data SALIDA2;
    cadena1 = "2021/07/15";
    fecha2 = put(input(cadena,yymmdd10.),date9.);
    cadena2 = "20220115";
    fecha2 = input(put(cadena,z8.),yymmdd8.);
run;

Otra solución es tratar la cadena de texto obteniendo cada uno de los tres componentes de la fecha: día, mes, año y pasándolos como parámetro a una función mdy() para formar la fecha.

data SALIDA;
    cadena = "2021/07/15";
    dia = input(put(substr(cadena,9,2),$2.),8.);
    mes = input(put(substr(cadena,6,2),$2.),8.);
    anyo = input(put(substr(cadena,1,4),$4.),8.);
    fecha = put(mdy(mes,dia,anyo),date9.);
run;

Este enfoque es engorroso y largo pero da total libertad al programador: Por ejemplo, en caso de que la entrada no sea exactamente una fecha, sino un dato como «202107» pero sin embargo quisiéramos conseguir una salida que fuera el primer día del mes.

data SALIDA;
    cadena = "202107";
    mes = input(put(substr(cadena,5,2),$2.),8.);
    anyo = input(put(substr(cadena,1,4),$4.),8.);
    fecha = put(mdy(mes,1,anyo),date9.);
run;

Macro SAS: Validar teléfonos (España)

Esta macro que incluyo a continuación permite validar número de teléfonos de España, aunque no se encuentre normalizados, esto es, contengan el prefijo internacional o no, o estén separados por algún tipo de separador o de espacio. Al final se ofrecen los números de teléfonos normalizados y sin prefijo.

Esta macro solo hace una verificación del formato del número del teléfono, pero no sabe distinguir entre números reales o no. Para hacer este tipo de verificaciones tenemos servicios que dan información vía API (protocolo de comunicación entre sistemas) devolviendo un fichero JSON sobre el tipo de teléfono, operador, país, etc. Espero poder poner un ejemplo sobre cómo integrar un servicio de terceros como este en un futuro (Hazme un comentario si estás interesado en ello).

%macro telefono(tabla=,campo=);
data &tabla;
set &tabla;
&campo = compress(&campo,' ','P');
if substr(&campo,1,4) = '0034' then &campo = substr(&campo,5);
if substr(&campo,1,2) = '34' then &campo = substr(&campo,3);
if substr(&campo,1,1) not in ('6','7','8','9') or
substr(&campo,1,2) in ('80','90') or
length(&campo) ne 9 then ind_valido = 0;
else ind_valido = 1;
run;
%mend;

Puedes probar con estos ejemplos la macro:

data clientes;
    format telefono $20.;
    input telefono;
    datalines;
    9851246323
    981122044
    +34666777888
    902123123
    0034-616875794
    +0034 615 789 456
    658.452.187
    001 32157979
    34715845978
    888123456
    (95)6543232
    ;
run;*/

%telefono(tabla=clientes,campo=telefono);

Truco SAS: Convertir fechas datetime de Unix a SAS

Como sabemos, los formatos de fechas en SAS y en otros sistemas operativos o bases de datos se interpretan y se almacenan como números que representan el número de días (o de segundos, en el caso de los datetime) transcurridos desde una fecha de origen. Las fechas anteriores a ella se almacenan como números negativos.

Lo que puede cambiar entre estos distintos sistemas es esa fecha origen a partir de la que se empieza a contar, de forma que una misma cifra puede representar fechas distintas dependiendo de qué sistema haya generado esa fecha. Esto da lugar a algunos problemas que debemos resolver cuando se realizan migraciones de datos de un sistema a otro. En nuestro caso, como programadores en SAS, debemos tener cuidado al importar fechas generadas con el timestamp de Unix y tener en cuenta que Unix no comienza a contar las fechas desde la misma fecha origen que SAS. Mientras SAS comienza a contar en ’01jan1960′; Unix comienza a contar en ’01jan1970′. Esos 10 años de diferencias hay que tenerlos en cuenta.

Por ello, si tenemos un datetime generado en Unix será necesario aplicar la siguiente corrección para que al convertir el número en fecha SAS éstas sean equivalentes:

fecha_SAS = fecha_Unix + 315619200;

Esa cantidad que sumamos es el número de segundos que representan esos 10 años. Existe otra forma de hacerlo, quizás un poco más elegante, que es utilizando la función dhms():

fecha_SAS = dhms('01jan1970'd, 0, 0, fecha_Unix);

La función dhms() construye un datetime a partir de una fecha y un número de horas, minutos y segundos. Con la instrucción anterior, se generará un datetime sumando el número de segundos expresados en la fecha_Unix a la fecha origen de Unix, que es el ’01jan1970′. Es una forma más elegante y además resulta más fácil recordar la fecha de origen de Unix, que el número tan grande de segundos que representan esa diferencia.
Si las fechas de Unix están representadas en milisegundos solo debemos utilizar una pequeña variante sobre lo anterior:

fecha_SAS = dhms('01jan1970'd, 0, 0, fecha_Unix/1000);

Trucos SAS: Añadir ceros por la izquierda

Estos son unos pequeños trucos para conseguir añadir tantos ceros por la izquierda como necesitemos para conseguir un tamaño dado para una cadena. Vamos a imaginar, para los ejemplos que vamos a poner, que necesitamos conseguir que nuestro valor tenga siempre 10 caracteres y que en caso de necesitarlo le añadiremos tantos ceros por la izquierda como sea necesario.
Lo primero, no es lo mismo si partimos de un número o de una cadena de texto. Disponemos de un método para cada uno de esos casos. En todo caso, la salida del proceso tiene que ser siempre una cadena de texto.

Añadir ceros por la izquierda a un número

Creamos una tabla NUMEROS con unos cuantos números de ejemplo de distintas longitudes. Deseamos crear una cadena de texto que tenga 10 posiciones y que, en caso de necesitarlo, se complete con tantos ceros por la izquierda como haga falta.

data NUMEROS;
    format num best12.;
    informat num best12.;
    input num;
    datalines;
    646543589
    9684634311
    654634
    6354
    1
run;

data NUM_FORMAT;
    set NUMEROS;
    format cadena $10.;
    cadena = put(num,z10.);
run;

Lo que hemos hecho para resolverlo es un casting del campo original. Con put() le hemos aplicado un formato z10., que es el que se encarga de añadirle ceros por la izquierda. La salida de esto es una cadena de texto, ya que si almacenamos un número, los ceros por la izquierda se perderán.

Añadir ceros por la izquierda a una cadena de texto

En caso de que partamos de una cadena de texto hay varias soluciones posibles, si bien, alguna de ellos no es válida si tenemos caracteres alfanuméricos en la cadena origen. Incluyo algunas en el siguiente código incluyéndolas como distintos campo de la tabla de salida:

data CADENAS;
    format char $10.;
    informat char $10.;
    input char;
    datalines;
    615546241
    A165432113
    135485
    A
run;

data CHAR_FORMAT;
    set CADENAS;
    format cadena1 cadena2 $10.;
    cadena1 = compress(repeat('0',9 - length(char))||char);
    cadena2 = put(input(char,best12.),z10.);
run;

La primera solución utiliza la función repeat() que incluye tantos caracteres, ‘0’ en este caso, como se le indique. Aquí se da una particularidad que no es común en SAS: si se indica 2 se añadirán 3 ceros, por lo que hay que restar 1 a la cantidad de caracteres que queremos añadir. Por ello, el segundo parámetro de repeat() está calculado como 9 (en vez de 10) menos la longitud original de la cadena. Concatenando la salida del repeat() a la cadena original tendremos lo que estamos buscando.
Otra opción es una solución menos general porque solo es válida si la cadena de entrada está formada solo por números. Esto es porque lo que haremos será reformatear la cadena de texto primero como número (formato best12.) para luego convertirla de nuevo en texto con el formato z10. que es el que vimos que se encarga de añadir ceros por la izquierda a números. Tenemos que usar una combinación de put() e input() ya que cada uno de ellos acepta números o cadenas de texto respectivamente.

Salida del código anterior

Macro SAS: Algoritmo para validar el código de cuenta bancaria (IBAN)

Aquí tenemos una macro que sirve para validar el código de cuenta bancario, IBAN, que rige en muchos países de Europa, Caribe y Medio Oriente. Se le pasan como parámetros el nombre de la tabla y el nombre del campo que contiene el IBAN en formato electrónico (todos seguido, sin espacios) y devuelve un indicador de si es válido o no y el nomobre de la entidad bancaria a la que pertenece.

El código IBAN tiene tres partes, los dos primeros dígitos corresponden al país, los dos segundos son el código de verificación que se calculan en función de todos los demás y que validan que el código se ha escrito bien; y una última parte que es variable. Cada país define la estructura de esta tercera parte que es un código alfanumérico de hasta 30 caracteres, ya que el código IBAN tiene un máximo de 34 caracteres totales.

En el caso de España, esta tercera parte del IBAN corresponde a 4 dígitos para el código de la entidad bancaria tal y como se lo asigna el Banco de España, otros 4 dígitos para el código de la oficina bancaria, los dos siguientes corresponden al código de verificación del antiguo CCC (el antiguo número de cuenta que teníamos en España), y finalmente 10 caracteres para el número de cuenta bancaria. El total de caracteres para España son 24.

Esta macro valida el código del país, que tiene que estar incluido en la lista de países adjunta a este post, el código de oficina bancaria para los IBAN españoles (también según listado adjunto). Será necesario crear las tabla SAS PAISES y BANCOS respectivamente para que la macro funcione plenamente. Lamentablemente, solo valida códigos IBAN españoles.

%macro normalizar_ccc(tabla=,campo=);
    /* Identificar país */
    data norm1;
        set &tabla;
        &campo = upcase(&campo);
        pais = substr(&campo,1,2);
        verificador = substr(&campo,3,2);
        resto = substr(&campo,5);
    run;

    /* Verificando pais y longitud */
    proc sql;
        create table norm2 as
        select a.*,
               longitud,
               case when length(&campo) ne longitud then 0
                    when b.codigo_pais = '' then 0
                    else 1 end as ind_valido
        from norm1 a
        left join IBAN b
        on a.pais = b.codigo_pais and a.pais = 'ES';
    quit;

    /* Verificar banco */
    proc sql;
        create table norm3 as
        select a.*,
               b.banco
        from norm2 a
        left join BANCOS b
        on substr(&campo,5,4) = input(put(b.codigo_banco,z4.),$4.);
    quit;

    data norm4;
        set norm3;
        if banco = '' then ind_valido = 0;
    run;

    /* validar codigo control */
    data norm5 (drop=calculo: modulo);
        set norm4;
        format calculo1 calculo2 calculo3 32.;
        if ind_valido = 1 and longitud = 24 then do;
            calculo1 = 0;
            calculo2 = 0;
            %do x = 5 %to 17;
                calculo1 = calculo1 * 10;
                if anyalpha(substr(&campo,&x,1)) = 1 then do;
                    calculo1 = calculo1 * 10;
                    calculo1 = calculo1 + rank(substr(&campo,&x,1))-55;
                end;
                else do;
                    calculo1 = calculo1 + put(substr(&campo,&x,1),8.);
                end;
            %end;
            modulo = mod(calculo1,97);
            %do x = 18 %to 24;
                calculo2 = calculo2 * 10;
                if anyalpha(substr(&campo,&x,1)) = 1 then do;
                    calculo2 = calculo2 * 10;
                    calculo2 = calculo2 + rank(substr(&campo,&x,1))-55;
                end;
                else do;
                    calculo2 = calculo2 + put(substr(&campo,&x,1),8.);
                end;
            %end;
            %do x = 1 %to 4;
                calculo2 = calculo2 * 10;
                if anyalpha(substr(&campo,&x,1)) = 1 then do;
                    calculo2 = calculo2 * 10;
                    calculo2 = calculo2 + rank(substr(&campo,&x,1))-55;
                end;
                else do;
                    calculo2 = calculo2 + put(substr(&campo,&x,1),8.);
                end;
            %end;
            calculo3 = (modulo * (10**ceil(log10(round(calculo2))))) + calculo2;
            modulo = mod(calculo3,97);
            if modulo ne 1 then do;
                ind_valido = 0;
                banco = "";
            end;
        end;
    run;

    /* salida */
    proc sql;
        create table &tabla._ as
        select a.*, ind_valido, banco
        from &tabla a
        left join norm5 b
        on a.&campo = b.&campo;
    quit;
%mend normalizar_ccc;

Descárgate este Excel que contiene los datos que debes incluir en la tabla PAISES y BANCOS:

El algoritmo para validar el IBAN partiendo del número de cuenta, consiste en lo siguiente:
1) Colocer los cuatro primeros caracteres detrás de todos los demás.
2) Sustituir todos los caracteres alfanuméricos que pueda haber por un equivalente numérico de 2 cifras de forma que A=10, B=11, C=12, etc.
3) Dividir la cifre restante entre 97 y tomar el resto de esa división.
4) Si el resto es un 1, entonces el IBAN es correcto.

Para validar la macro con algunos ejemplos de prueba, puedes utilizar lo siguiente:

data CUENTAS;
    format ccc $50.;
    length ccc $ 50;
    input ccc $;
    datalines;
ES6621000418401234567891
ES6000491500051234567892
ES9420805801101234567891
ES90002469125012345678910
ES7100302053091234567895
EL71003020530943574567895
ES1000492352082414205416
ES1720852066623456789011
ES4501986746463463463547
ES450198674646D463463547
ES450198674646&463463547
ES450198674646.463463547
ES1346657676431313200000
run;

%normalizar_ccc(tabla=CUENTAS,campo=ccc);