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.

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.

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 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: 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);

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

La función intnx() es una de las funciones más útiles, junto con intck(), para el trabajo con fechas y merece por si misma una entrada en el blog. Vamos a empezar viendo su sintaxis básica y su uso:

Sintáxis

intnx(intervalo, fecha, incremento)
Esta función calcula de una forma fácil la fecha que se deriva de sumar cierta cantidad (incremento) de periodos de tiempo (intervalo) a una fecha concreta. Por ejemplo, intnx('month','1jan2020'd,5) da como resultado el 1 de junio de 2020, porque si a partir del 1 de enero sumamos 5 meses es el 1 de junio. Esto es tremendamente útil porque sumar meses considerándolos como 30 días no da una fecha concreta del mes que buscamos si no que varía en función de la cantidad de días que tengan los meses intermedios.

Con respecto a los intervalos que podemos usar están, entre otros: ‘day’, ‘week’, ‘month’, ‘qtr’ (trimestre), ‘year’. La fecha debe incluirse en formato numérico (no vale una cadena de texto) y el incremento es cualquier número entero positivo o negativo.

intnx(‘month’,’1jul2021’d,-7) ‘1jan2021’d
intnx(‘day’,’15jul2021’d,-15) ‘1jul2021’d
intnx(‘week’,’11oct2021’d,-1) ‘4oct2021’d
intnx(‘year’,’1jan2021’d,-1) ‘1jan2020’d
intnx(‘qtr’,’1jan2021’d,-1) ‘1oct2020’d

El alineamiento

Adicionalmente disponemos de un cuarto parámetro; el alineamiento, que indica en qué parte del intervalo temporal que hemos definido queremos situarnos: al principio, a la mitad, al final o el mismo día que la fecha indicada. El alineamiento tomará respectivamente los valores: ‘B’, ‘M’, ‘E’, ‘S’, por sus siglas en inglés. El valor ‘B’ es el valor por defecto si no se indica nada.

intnx(‘year’,’6jul2021’d,0) ‘1jan2021’d Como ‘B’ es el valor por defecto, si no indicamos nada obtendremos el primer día del mismo (0) periodo (year) de la fecha indicada (‘6jul2021’).
intnx(‘year’,’6jul2021’d,0,’E’) ’31dec2021’d Cuando se indica ‘E’ obtendremos el último día del mismo (0) periodo (year) a la fecha indicada (‘6jul2021’).
intnx(‘year’,’6jul2021’d,1,’S’) ‘6jul2022’d Con ‘S’ obtendremos la misma fecha del siguiente periodo.
intnx(‘week’,’6jul2021’d,1,’S’) ’13jul2021’d En este caso, como se indican sumar un periodo de una semana y tenemos el alineamiento nos indica que debe ser el mismo día de la semana (martes), obtendremos la fecha de 7 días después.
intnx(‘week’,’6jul2021’d,1,’E’) ’17jul2021’d Utilizar el alineamiento ‘E’ indica tener que ir al último día de la siguiente semana, el sábado. Las semanas comienzan el domingo.

Con todo esto, ¿cómo obtener los registros correspondientes al mes anterior de una tabla cuyos registros están informados con un campo fecha?
intnx('month',date(),-1,'B') <= fecha <= intnx('month',date(),-1,'E')
Esta condición permite obtener todos los registros del mes anterior al actual de una tabla. Lo mismo sería para obtener la semana anterior o el trimestre anterior.

Multiplicadores de intervalo

Finalmente, existe la posibilidad de añadir multiplicadores a los intervalos. Para ello, se añade simplemente un número detrás del nombre del intervalo. Por ejemplo: intnx('month3',date(),1) sería una forma de indicar la fecha correspondiente al inicio del próximo trimestre y es equivalente a expresarlo como intnx('qtr',date(),1).

intnx(‘month3’,’1jan2021’d,1) ‘1apr2021’d Obtenemos el primer día del trimestre siguiente al actual.
intnx(‘month2’,’1jan2021’d,1) ‘1mar2021’d Utilizando el multiplicador 2 con el mes obtendremos periodos de 2 meses.
intnx(‘month2.2′,’1mar2021’d,0,’B’) ‘1feb2021’d Aquí utilizamos el multiplicador para definir periodos temporales de dos meses, pero lo hemos modificado con un índice «.2» que indica que estos periodos temporales de dos meses comienzan el segundo mes del año. Por tento la fecha de inicio de ese periodo es el 1 de febrero.
intnx(‘month2.2′,’1mar2021’d,0,’E’) ’31mar2021’d Al igual que en el ejemplo anterior, utilizamos el mismo multiplicador y pedimos la última fecha de ese periodo que es el 31 de marzo. ‘month2.1’ sería equivalente a ‘month2’.

Intervalos personalizados

Vistas todas las opciones que nos ofrece predefinidas intnx() aún podemos definir intervalos propios en vez de los conocidos ‘month’ o ‘year’. Pongámonos en el caso de una empresa que realice los cierres mensuales el día 16 de cada mes. Una opción sería utilizar el intervalo predefinido ‘semimonth2.2’.

intnx(‘semimonth2.2′,’1jan2021’d,0,’B’) ’16dec2020’d
intnx(‘semimonth2.2′,’1jan2021’d,0,’E’) ’15jan2021’d

Sin embargo, esta empresa tiene una particularidad, y es que los meses de febrero cierran el día 14, no el 15. Para resolver esto tenemos dos opciones o añadir una lógica condicionada al mes en curso o definir nuestro propio intervalo. Para definir nuestro propio intervalo utilizaremos la instrucción options intervalds a la que se le pasan el nombre del intervalo y el de la tabla que contiene la definición de esos intervalos. Luego definimos esa tabla de la manera en que se muestra y ya podremos utilizar nuestros propios intervalos con la función intnx() y de la misma manera que los predefinidos:

options intervalds=(cierre=dstest);
data dstest;
format begin end date9.;
begin='16jan2021'd; end='14feb2021'd; output;
begin='15feb2021'd; end='15mar2021'd; output;
begin='16mar2021'd; end='15apr2021'd; output;
begin='16apr2021'd; end='15jun2021'd; output;
begin='16jun2021'd; end='15jul2021'd; output;
begin='16jul2021'd; end='15aug2021'd; output;
begin='16aug2021'd; end='15sep2021'd; output;
begin='16sep2021'd; end='15oct2021'd; output;
begin='16oct2021'd; end='15nov2021'd; output;
begin='16nov2021'd; end='15dec2021'd; output;
begin='16dec2021'd; end='15jan2022'd; output;
begin='16jan2022'd; end='14feb2022'd; output;
begin='15feb2022'd; end='15mar2022'd; output;
begin='16mar2022'd; end='15apr2022'd; output;
begin='16apr2022'd; end='15jun2022'd; output;
begin='16jun2022'd; end='15jul2022'd; output;
begin='16jul2022'd; end='15aug2022'd; output;
begin='16aug2022'd; end='15sep2022'd; output;
begin='16sep2022'd; end='15oct2022'd; output;
begin='16oct2022'd; end='15nov2022'd; output;
begin='16nov2022'd; end='15dec2022'd; output;
begin='16dec2022'd; end='15jan2023'd; output;
run;

data salida;
format fecha_inicio fecha_fin date9.;
fecha_inicio=intnx('cierre',date(),0,'B');
fecha_fin=intnx('cierre',date(),0,'E');
run;

Macro SAS: Validar y normalizar el DNI

Comparto con vosotros esta macro que realiza una validación del campo DNI de una tabla. Admite como parámetros el nombre de la tabla (DNIS) y el nombre del campo con el DNI (DNI). Como salida genera otra tabla que se llama DNIS_ (añade un subrallado al final) y le añade el campo dni_norm. La macro normaliza DNIs y NIEs españoles.

En el primer paso se verifica el formato del DNI y se rechazan los que tengan un formato que no sea compatible. Estos se marcan con el indicador ind_valido. Para aquellos que cumplen con el formato básico de los DNIs, trocea ese DNI en prefix, number y sufix, De forma que se validan por separado.

En el segundo paso se normaliza la parte numérica del DNI dándole tantos dígitos como vaya a necesitar añadiendo ceros por la izquierda. En el tercer paso se calcula la letra del DNI/NIE y se juntan todos los trozos para tener el DNI normalizado. En un último paso se realiza el cruce que la tabla inicial de DNIs.

%macro normalizar_dni(tabla=,campo=);
    /* obtenemos las partes del DNI y localizamos formatos incorrectos */
    data norm1;
        set &tabla;
        &campo = upcase(&campo);
        if anypunct(&campo) > 0 then ind_valido = 0;
        else if length(&campo) > 9 then ind_valido = 0;
        else if 0 < anyalpha(substr(&campo,2)) < length(substr(&campo,2)) then ind_valido = 0;
        else if compress(substr(&campo,1,1),'XYZ','D') ne '' then ind_valido = 0;
        else if length(&campo)=9 and anyalpha(&campo)=0 and substr(&campo,1,1)='0' then do;
            &campo=substr(&campo,2);
            ind_valido = 1;
            prefix = compress(substr(&campo,1,1),'','D');
            number = input(compress(&campo,'','A'),8.);
            sufix = compress(substr(&campo,length(&campo)),'','D');
        end;
        else if length(&campo)=9 and anyalpha(&campo)=0 then ind_valido = 0;
        else do;
            ind_valido = 1;
            prefix = compress(substr(&campo,1,1),'','D');
            number = input(compress(&campo,'','A'),8.);
            sufix = compress(substr(&campo,length(&campo)),'','D');
        end;
    run;

    /* normalizamos el número */
    data norm2;
        set norm1;
        format numero $8.;
        length numero $ 8;
        n = number;
        if anyalpha(prefix)=1 then numero = put(number,z7.);
            else numero = put(number,z8.);
    run;

    /* calculamos la letra del DNI y verificamos si es correcta cuando venga informada */
    data norm3 (drop=prefix number sufix numero n letras resto letra_norm ind_valido);
        set norm2;
        letras = 'TRWAGMYFPDXBNJZSQVHLCKE';
        if prefix='Y' then n=n+10000000;
        if prefix='Z' then n=n+20000000;
        resto = mod(n,23);
        letra_norm = substr(letras,resto+1,1);
        dni_norm = compress(prefix||numero||letra_norm);
    run;

    /* salida */
    proc sql;
        create table &tabla._ as
        select a.*, dni_norm
        from &tabla a
        left join norm3 b
        on a.&campo = b.&campo;
    quit;
%mend normalizar_dni;

Podéis probarla con este set de datos de prueba que os dejo aquí:

data DNIS;
    format dni $20.;
    length dni $ 20;
    input dni $;
    datalines;
    16634732A
    1b
    16634732
    32631459w
    X00123
    Y1234612
    123&134
    Z1224536
    X00z12
    064563314
    6843131058
    1635740.65
    000000315
    G12
run;

%normalizar_dni(tabla=DNIS,campo=dni);

SAS: Obtener el listado de los últimos ficheros por fecha en Unix

¿Cómo podemos obtener y cargar los últimos archivos de un cierto periodo? Seguro que podemos encontrarnos este problema en algunas de sus variantes y podemos resolver todas ellas basándonos de la siguiente forma. Voy a basar este artículo en el supuesto de una máquina SAS corriendo sobre un sistema operativo Unix, que creo que es lo más común, aunque tiene también la misma solución bajo Windows, con algunas diferencias.

Lo primero que vamos a hacer es obtener el listado de ficheros csv que nos interesa cargar, utilizando la línea de comandos del sistema operativo del servidor SAS, y vamos a incluir ese listado en un fichero lista_ficheros.txt. Utilizaremos el comando x de SAS que envía las instrucciones al sistema operativo para que este los interprete.

x"cd /[ruta_ficheros]";
x"ls *.csv > lista_ficheros.txt";

En el paso anterior el comando «cd» indica la ruta en la vamos a trabajar, que será la ruta en donde tenemos guardados esos ficheros. El comando «ls» lista los ficheros que cumplan con el patrón indicado y la salida la vuelca en un fichero nuevo llamado lista_ficheros.txt. Sin embargo, el paso anterior no discrimina los ficheros por fecha y para poder obtener, por ejemplo, los ficheros de los últimos 15 días utilizaremos lo siguiente:

x"cd /[ruta_ficheros]";
x"find *.csv -mtime +15 > lista_ficheros.txt";

El comando «find» busca todos aquellos ficheros en la ruta indicada que cumplan el patrón de nombre de fichero y que estén en el periodo temporal indicado en «-mtime» volcando todos los nombres de esos ficheros en un nuevo fichero llamado listado_ficheros.txt. Una vez llegado a este punto solo nos queda importar el fichero txt como una tabla SAS para poder procesar la información que contiene fácilmente:

data LISTA_FICHEROS;
    length fichero $ 50 ;
    format fichero $CHAR50. ;
    informat fichero $CHAR50. ;
    infile "[ruta_ficheros]/ficheros.txt"
        lrecl=50 encoding="LATIN9" missover;
    input fichero : $CHAR50. ;
run;

Ahora tenemos una tabla (LISTA_FICHEROS) que contiene una lista de nombres de ficheros csv que queremos importar. Como son varios, más de uno, debemos utilizar un bucle para recorrer la tabla de los nombres a la vez que importamos los csv y los vamos guardando como tablas SAS. Pero, ¿cómo se hace eso? ¿Cómo podemos recorrer una tabla a la vez que creamos otra? Utilizaremos para recorrer la primera tabla una especie de puntero que recorre LISTA_FICHEROS:

%let dsid = %sysfunc(open(LISTA_FICHEROS));
%macro importar_ficheros;
    %let num_reg = %sysfunc(attrn(&dsid,NOBS));
    %do x=1 %to &num_reg;
        %let rc = %sysfunc(fetch(&dsid));
        %let fichero = %sysfunc(getvarc(&dsid,%sysfunc(varnum(&dsid,F1))));

        data FICHERO_&x;
            length campo $ 20;
            format campo $CHAR20. ;
            informat campo $CHAR20. ;
            infile"&fichero"
                lrecl=20 encoding="UTF8" missover;
            input campo : $CHAR20. ;
        run;
%mend importar_ficheros;
%importar_ficheros;

Sobre como utilizar estos punteros de datos en SAS que hemos utilizado en este último paso podéis revisar este link: «Punteros de datos en SAS» donde se explican los distintos comandos que se utilizan para gestionar los punteros.

Por otro lado, el bucle anterior va recogiendo el nombre de cada fichero csv que está listado en la tabla LISTA_FICHEROS e importándolo a una tabla SAS. En este supuesto todos los ficheros son del mismo formato.

SAS: Procedimiento proc contents

El procedimiento proc contents sirve para obtener información de las tablas y librerías. Produce un informe que puede presentarse en la pestaña output de SAS Enterprise Guide o puede volcarse todo en una tabla con el parámetro out=. Volcar esa información en una tabla permite utilizar la información recogida en tiempo de ejecución.
Pero, vamos a empezar por el principio:

proc contents data=SASHELP.CLASS;
run;

En este ejemplo utilizamos la tabla CLASS de la librería de ejemplos de SAS (SASHELP) que está presente en las instalaciones de SAS por defecto para obtener un informe en la pestaña «output» del contenido de la tabla. Entre otras cosas que no incluyo aparece un listado de los campos de la tabla:

Podemos enviar este listado de campos, junto con otra información adicional relevante a una tabla SAS, en vez de a ese informe. Para ello determinamos una tabla de salida con out=. Y como no nos interesa la salida en el informe, además incluimos el parámetro noprint:

proc contents data=SASHELP.CLASS
    out=SALIDA noprint;
run;

En la tabla que hemos obtenido tendremos la siguiente información (entre otra):

Variable Explicación
LIBNAME Nombre de la librería donde se encuentra la tabla.
NAME Nombre del campo de la tabla.
TYPE Código numérico que indica de que tipo es la variable. 1 = numérico o fecha y 2 = texto.
LENGTH Tamaño del campo en bytes.
VARNUM El número de orden de la variable dentro de la tabla. Es recomendable ordenar la salida por esta variable.

Podremos utilizar la información aquí almacenada para saber por ejemplo si la tabla que hemos cargado tiene un campo ID como número o como texto; o si una fecha que hemos importado ha quedado como fecha o simplemente se ha quedado como una cadena de texto. Podremos resolver esos problemas de importación desde aquí.
En siguientes artículos explicaré como podemos solucionar cada uno de esos problemas en un artículo con un código SAS completo.

SAS: Operaciones con cadenas de texto (y 2)

Continúo comentando algunas funciones de SAS para utilizar con cadenas de texto. Existen muchas más que estas que menciono, pero estas me parecen más interesantes.

Este bloque de funciones tratan la cadena de texto dando como salida un número que bien indica una posición o un tamaño de la cadena. Lo veremos utilizando este ejemplo:

%let cad = '  En 1 lugar de la  Mancha...  ';
Función Resultado Comentario
count(&cad, ‘a’) 4 Devuelve el número de veces que aparece un carácter en una cadena dada. En el caso del ejemplo se busca contar el número de aes en la cadena.
length(&cad) 31 Devuelve el número de caracteres totales de la cadena que se indica.
anydigit(&cad) 6 Devuelve un número que representa la posición del primer dígito que aparezca en una cadena que le hemos indicado.
anyalpha(&cad) 3 Esta función hace lo mismo que la anterior, pero buscando el primer carácter de la cadena.
anypunct(&cad) 27 Finalmente, esta función hace lo mismo que las anteriores buscando signos de puntuación u otros caracteres no alfanuméricos. Como se puede ver en el ejemplo, los espacios no se consideran signos de puntuación, así que la posición que está devolviendo la función aquí es la del primer punto del final.

A continuación vamos a profundizar un poco en las distintas variantes de las funciones del tipo index que ya hemos visto en la entrada anterior. Existen 3 funciones de este tipo que son interesantes, creo, para procesar texto: index, indexc, indexw y las explicamos a continuación con el siguiente ejemplo de cadena:

%let cad = 'He quedado el 1º, por lo que estoy muy contento';
Función Resultado Comentario
index(&cad, ‘que’) 4 Devuelve la posición de la primera ocurrencia de la subcadena indicada. La subcadena tiene que ser exactamente igual en mayúsculas y minúsculas. Puede encontrar esa ocurrencia como palabra independiente o como parte de una palabra mayor.
indexw(&cad, ‘que’) 26 Devuelve la posición de la subcadena indicada, siempre que se encuentre como una palabra independiente.
indexc(&cad, ‘0123456789’) 15 Devuelve la posición del primero de los caracteres que encuentre incluido en la lista que aparece en el segundo parámetro. Esta función así expresada es equivalente al ejemplo que he dado para la función anydigit() del bloque anterior.

Finalmente, una curiosidad, la función reverse(cadena) que reordena los caracteres que la componen invirtiendo su orden. El último carácter pasará a ser el primero; el penúltimo será el segundo y así sucesivamente. Nunca la he utilizado, no imagino cómo, pero me resulta una curiosidad.

Si tenéis cualquier duda que queráis dirigirme, escribid un comentario y os contestaré con una solución.