Tengo una serie de columnas tituladas Income_2015, Interest 2015, Dividends 2015, Income_2018, Interest 2018, Dividends 2018 etc. y quiero crear columnas totales para cada año.

¿Hay alguna manera de hacer esto en SAS sin tener que enumerar individualmente todas las columnas en la suma

Actualmente uso lo siguiente para obtener totales por tipo:

data keep;
set from;

income_total = sum(of Income_:);
run;

No puedo encontrar una forma de totalizar por año a menos que use:

year_2015_total = sum(Income_2015, Interest_2015, Dividends_2015)

.... year_2018_total = sum(Income_2018, Interest_2018, Dividends_2018)

Aprecio que podría hacer lo anterior, pero esto es solo una muestra de las columnas que necesito incluir. La lista actual es mucho más larga.

1
paulandjules 10 may. 2019 a las 10:38

3 respuestas

La mejor respuesta

No existe una manera fácil / automática de definir una lista de variables por el sufijo. Es decir, no hay nada directamente equivalente a sum (of :2018).

Dicho eso, tienes opciones. Una es usar matrices. Le permiten hacer referencia a una lista de variables por el nombre de la matriz.

Otra es usar el lenguaje macro. Eso le permitirá construir una variable macro que contiene la lista de todos los nombres de variables que tienen un sufijo particular.

Pero creo que la mejor solución es reestructurar sus datos.

Si sus datos ahora se ven así:

Person Income_2015 Interest_2015 Income_2016 Interest_2016
1      100,000     1,000         200,000     2,000
2      500,000     5,000         600,000     6,000

Transponerlo a:

Person Year        Income      Interest
1      2015        100,000     1,000
1      2016        200,000     2,000
2      2015        500,000     5,000
2      2016        600,000     6,000

Esa estructura vertical hace que sea mucho más fácil trabajar con los datos, y puede agregar fácilmente a través de años (columnas) o fuentes de ingresos (filas). El punto clave es que el año es realmente un valor de datos. Por lo tanto, la estructura vertical ("normalizada") lo beneficia al almacenar el año como un valor en una variable, en lugar de almacenar el año como parte de un nombre de variable.

0
Quentin 10 may. 2019 a las 12:21

Podría intentar usar variables macro, como:

proc sql;
    select name into:list_2015 separated by ',' from dictionary.columns where libname='YOURLIB' and memname='YOURDATA' and name contains '2015';
quit;

data want;
  set have;
  sum_2015=sum(of &list_2015);
run;
0
Shenglin Chen 10 may. 2019 a las 12:32

Lamentablemente, su diseño de datos ha almacenado el elemento de datos como parte de los metadatos (una parte de los nombres de las variables) y obtiene un conjunto de datos que se amplía.

La forma categórica es adecuada para las agregaciones acumuladas y el uso de procedimientos de informes como TABULATE o REPORT para generar la vista 'amplia' de los datos.

id topic     year  amount
a  income    2015  
a  interest  2015  
a  dividends 2015
a  losses    2015
b  income    2015  
b  interest  2015  
b  dividends 2015
                    … no losses row for id=b corresponds to 'missing' value in wide form
c  income    2015  
c  interest  2015  
                    … c had no dividends, no losses, no fubars

Manteniendo la estructura sin cambios (algunas veces solo tiene que hacerlo, como datos de mainframe de diez millones de cuentas que abarcan 60 años), necesitaría preprocesar el conjunto de datos con un paso que identifica las columnas que contienen un año como valor y usar esa información en un código de paso posterior que contiene generación de código (es decir, macro) que cubre los años que se encuentran en los nombres de columna. Por ejemplo:

%macro make_data(data=);
  %local year category index varname;

  data &data;
    do accountid = 1 to 20;
      %do index = 65 %to 90;
      %do year=1995 %to 2019;
        %let varname = %sysfunc(byte(&index))_&year;
        amount+1;
        &varname = amount;
      %end;
      %end;
      output;
    end;
    drop amount;
  run;
%mend;

%macro generate_year_total_code(data=);
  proc contents noprint data=&data out=havemeta(keep=name);
  run;

  data havemeta2;
    set havemeta;
    if prxmatch ("/[^0-9]\d{4}/", trim(name));
    year = input(substr(name,length(name)-3), 4.);
  run;

  proc sort data=havemeta2;
    by year;
  run;

  data _null_;
    length varlist $32000;

    do until (last.year);
      set havemeta2;
      by year;

      varlist = catx(',', varlist, name);
    end;

    call symputx('year_count', _n_);
    call symput ( cats('total_statement_', _n_)
                , cats('total_',year,'=sum(',varlist,')'));
  run;
%mend;

%macro totals(data=);

  %generate_year_total_code(data=&data);

  %local index; 

  data &data;
    set &data;
    %do index = 1 %to &year_count;
      &&total_statement_&index;
    %end;
  run;

%mend;

%make_data(data=have);

options mprint;
%totals(data=have);

Una alternativa es transponer los datos en forma categórica para extraer el valor del año para el resumen o uso apropiado en informes categóricos. Por ejemplo:

proc transpose data=have out=haveTall;
  by accountid;
run;

data haveTall;
    set haveTall;
    if prxmatch ("/_\d{4}$/", trim(_name_));  * data value in col1 is from a year suffixed variable name;
    year = input(substr(_name_,length(_name_)-3), 4.);
    category = substr(_name_,1, length(_name_)-5);
    amount = col1;
    drop _name_ col1;
run;

proc tabulate data=haveTall;
  class accountid category year;
  var amount;
  table 
    accountid * category
    , 
    year=''*amount=''*sum=''
    /
    nocellmerge
    ;
run;

Una tercera forma más compleja, pero sucinta. Calcule dinámicamente los totales utilizando un objeto hash, genere los totales y vuelva a combinarlos con sus datos originales. Esto requiere más CPU que la forma # 1 porque los nombres de las variables PDV se evalúan en cada fila.

data _null_;
  if 0 then set have(keep=accountid);

  length year amount 8;
  declare hash totals(ordered:'a');
  totals.defineKey('accountid', 'year');
  totals.defineData('accountid', 'year', 'total');
  totals.defineDone();
  call missing (year, amount, accountid);

  do until (end);
    set have end=end;
    array numbers _numeric_;

    do over numbers;
      length name $32;
      name = vname(numbers);
      if prxmatch ("/_\d{4}$/", trim(name)) then do;
        year = input(substr(name,length(name)-3), 4.);
        if totals.find() = 0
          then total + numbers;
          else total = numbers;
        totals.replace();
      end;
    end;
  end;

  totals.output(dataset:'totals');

  stop;
run;

proc transpose data=totals prefix=total_ out=totals_across_year(drop=_name_);
  by accountid;
  var total;
  id year;
run;
0
Richard 10 may. 2019 a las 13:27