草庐IT

SAS编程-宏:固定分类顺序的频数汇总表

野藤_ 2023-03-28 原文

在临床试验TFL编程中,简单的描述性统计量与频数汇总表格的数量占表格总量的绝对大头。从提高编程效率的角度看,为这两类表格建立稳定的宏程序输出是一件非常高效率的事情。

更多临床试验SAS编程内容,欢迎关注:SAS茶谈。

这篇文章介绍,分类变量简单频数汇总宏程序的处理。主要有5方面的内容:

  1. 试验汇总组的处理
  2. BigN的生成
  3. 固定分类的Format设置
  4. 频数百分比的计算
  5. 横向数据转换为纵向数据

输出Table的样式,各家基本相同,这篇文章采用以下样式:

Layout

计算统计量的过程步选择Proc Means, 由于Means过程步只针对数值型变量进行分析,还需要新建一个数值变量用于计数(flag = 1)。

分析数据来源于SASHELP.CLASS数据集,简单处理下,新建一个分组变量:

data class0;
  set sashelp.class (in = a) 
      sashelp.class (in =  b);
  if a then trt01pn = 1;
  if b then trt01pn = 2;

  flag = 1;
run;

1. 试验汇总组的处理

TFLShell中会明说明输出Table是否有汇总组。最常见的处理汇总组的方式是,在原始数据集利用Data步的Output语句,新建汇总组;我推荐大家尝试使用Format过程步中的Multilabel选项进行构建汇总组,这个方法不需要在原始数据集中进行新建分组处理,一定程度减少了分析数据集的观测数。

具体介绍文章参考:SAS编程:生成Table时,汇总组(Total)组如何处理?

示例代码如下:

proc format;
  value trt01pn (notsorted multilabel)
    1 = 1      
    2 = 2  
    1,2 = 99
  ;
run;

2. BigN的生成

由于频数百分比的计算基于BigN,所以这里先计算输出BigN,并将BigN的值保存到宏变量中。使用Means过程步中Class语句的preloadfmtmlf选项,可以输出分类格式的结果,包括提前定义好的汇总组。

** Derive BigN and save them to macro vars;
proc means data = class0 nway completetypes;
  format trt01pn trt01pn.;
  class trt01pn / preloadfmt mlf order = data;
  var flag;
  output n = bign out = BigN;
run;

data _null_;
  set BigN;
  call symputx("N_"||strip(trt01pn), strip(put(bign, best.)));
run;

输出结果如下:

BigN

BigN输出到数据集中,方便之后与计数结果数据集进行拼接,计算百分比。

3. 固定分类的Format设置

在考虑固定分类Format设置之前,先确认下首行文字信息如何处理。之前文章提到过,常用的2种方法是,新建一个数据集与结果数据集纵向拼接,或是在结果数据集种多Output一行记录。

我这里提供另一种方法,利用Format过程步中Mlultilabel选项,新建汇总组,汇总组的Format值保留文字说明信息。如果首行只需要文字说明信息,不需要频数百分比,最后在结果数据集中删除首行中的频数百分比的内容。

考虑到固定顺序,为每一个Format值建立一个Informat的排序数值。

proc format;
  value $sex (notsorted multilabel)
    "M", "F" = "Sex - n (%)"
    "M" = "Male"
    "F" = "Female"
  ;

  invalue sexn
    "Sex - n (%)" = 0
    "Male" = 1
    "Female" = 2
  ;
run;

4. 频数百分比的计算

应用Means过程步进行计算分类频数,考虑到试验分组变量与分析分组变量都需要输出所有可能类别的结果,对这2个变量都需要使用Class语句中的preloadfmt选项

如果只需要输出数据集中已有的分类的结果,不需要输出未出现的可能结果,可以直接将分组变量设为by语句变量

**Get small n;
proc means data = class0 nway completetypes;
  format trt01pn trt01pn.;
  class trt01pn / preloadfmt mlf order = data;

  format sex $sex.;
  class sex/ preloadfmt mlf order = data;

  var flag;
  output n = count out = count1;
run;

输出结果如下:

Count 1

这里可以直接与BigN数据集进行拼接,计算出百分比。

**Get percentage;
data count2;
  merge count1 bign(keep = trt01pn bign);
  by trt01pn;

  section = 1;
  cat1n = input(sex, sexn.);

  length col1 $200;  
  col1 = sex;

  length freq $200;
  if bign ne 0 then freq = strip(put(count, best.))||" ("||strip(put(count/bign*100, 8.1))||")";
  else freq = "0 (-)";

  proc sort;
    by section cat1n col1 trt01pn;
run;

输出结果如下:

Count 2

这里百分比直接给了8.1的Format,如果公司或统计师有其他要求,可以直接定义好Format进行引用,例如以下两种格式:

proc format;
  value freq
    0-<0.1 = "<0.1"
    0.1-<99.95 = [4.1]
    99.95-100 = "100"
  ;

  prcture freq
    0-<0.1 = "<0.1" (noedit)
    0.1-<9.95 = "009.9)" (prefix="(  ")
    9.95-<99.95 = "009.9)" (prefix="( ")
    99.95-100 = "(100)  " (noedit)
  ;

run;

程序中只需更新下Format名称就好:

  if bign ne 0 then freq = strip(put(count, best.))||" ("||strip(put(count/bign*100, freq.))||")";

5. 横向数据转换为纵向数据

考虑代码与输出结果的简洁性,选用Transpose过程步进行转置,不再使用Data步中Output语句。

**Transpose results;
proc transpose data = count2 out = count3 prefix = trt_;
  by section cat1n col1;
  var freq;
  id trt01pn;
run;

输出结果如下:

Count 3

考虑到不需要第一行的文字说明信息,可以直接将信息置空:

**Create output dataset;
data count4;
  set count3;
  if cat1n = 0 then call missing(of trt_:);
  
  drop _name_;
run;
Count 4

最后的输出结果与Shell的中的内容相同。

6. 完整宏程序汇总

这个宏程序的参数出了输入的变量外,还有分类变量对应的Format,首行信息的内容也是通过Format来控制。读者也可以根据自己的需要进行宏的更新。

%macro catn(indt=, trtvar=, trtfmt=, catvar=, catfmt=, catnfmt=, section=, outdt=);

**Get small n;
proc means data = &indt. nway completetypes;
  format &trtvar.  &trtfmt..;
  class &trtvar./ preloadfmt mlf order = data;

  format &catvar. $&catfmt..;
  class &catvar./ preloadfmt mlf order = data;

  var flag;
  output n = count out = count1;
run;

**Get percentage;
data count2;
  merge count1 bign(keep = &trtvar. bign);
  by &trtvar.;

  section = &section.;
  cat1n = input(&catvar., &catnfmt..);

  length col1 $200;  
  col1 = &catvar.;

  length freq $200;
  if bign ne 0 then freq = strip(put(count, best.))||" ("||strip(put(count/bign*100, 8.1))||")";
  else freq = "0 (-)";

  proc sort;
    by section cat1n col1 &trtvar.;
run;

**Transpose results;
proc transpose data = count2 out = count3 prefix = trt_;
  by section cat1n col1;
  var freq;
  id &trtvar.;
run;

**Create output dataset;
data &outdt.;
  set count3;
  if cat1n = 0 then call missing(of trt_:);
  
  drop _name_;
run;

%mend catn;


proc format;
  value trt01pn (notsorted multilabel)
    1 = 1      
    2 = 2  
    1,2 = 99
  ;

  value $sex (notsorted multilabel)
    "M", "F" = "Sex - n (%)"
    "M" = "Male"
    "F" = "Female"
  ;

  invalue sexn
    "Sex - n (%)" = 0
    "Male" = 1
    "Female" = 2
  ;
run;

**Invoke the macro;
%catn(
  indt = class0
  ,trtvar = trt01pn
  ,trtfmt = trt01pn
  ,catvar = sex
  ,catfmt = sex
  ,catnfmt = sexn
  ,section = 1
  ,outdt = sec_1
);

以上程序运行,与之前结果一致。

7. 扩展与延伸

从宏的程序看,只设置了一个试验分组变量。这里可以有读者要问,如果输出表格需要多个试验分组变量,如何处理呢?

我建议,这种情况不使用宏程序,程序不复杂直接手动编写,简洁方便

宏程序一般处理重复的编程内容,例如,Baseline Demogrphic这类汇总表,有多个不同的分析变量,内容高度重复化,程序中宏程序会调用很多次。但对于多个分组变量的情形,有这样的宏,程序中也只会调用一次。在我看来,这是没有必要的。

当然,对于多个分组变量的亚组分析,每个亚组也可以看成是重复单一的内容。但是,这样简单机械的处理抹去了,各亚组之间的联系。这就像数学的几何题一样,我们可以通过复杂推理得到正确的结果,不过,有时候一条辅助线就可以让整个解题过程简洁的多得多。

这里举个LB频数汇总的例子,LB中可能有二三十个Parameter,如果单独依次出每个Paramter对应的内容,那过程就太过繁琐;如果在宏程序中添加多个分组变量,一次调用就能解决,那跟手动写出完整输出过程又没什么区别。

proc means data = adlb noprint nway completetypes;
  by trt01an parcat1n paramn paramcd avisitn;
  format anrindn anrindn.;
  class anrindn / preloadfmt order = data;

  var flag

  output n = count out = count1;
run;

当然,这种情况下,如果不了解频数表的输出的逻辑与过程,确实是直接调用宏程序,来得更简单高效一点。

总结

这篇文章介绍了,固定分类顺序的频数汇总表的宏程序输出。固定顺序主要通过,Means过程步的preloadfmt选项进行实现。

希望对读者日常编程工作,有所帮助。

感谢阅读, 欢迎关注:SAS茶谈!
若有疑问,欢迎评论交流!

有关SAS编程-宏:固定分类顺序的频数汇总表的更多相关文章

  1. ruby - Chef 执行非顺序配方 - 2

    我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul

  2. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  3. 网络编程套接字 - 2

    网络编程套接字网络编程基础知识理解源`IP`地址和目的`IP`地址理解源MAC地址和目的MAC地址认识端口号理解端口号和进程ID理解源端口号和目的端口号认识`TCP`协议认识`UDP`协议网络字节序socket编程接口`sockaddr``UDP`网络程序服务器端代码逻辑:需要用到的接口服务器端代码`udp`客户端代码逻辑`udp`客户端代码`TCP`网络程序服务器代码逻辑多个版本服务器单进程版本多进程版本多线程版本线程池版本服务器端代码客户端代码逻辑客户端代码TCP协议通讯流程TCP协议的客户端/服务器程序流程三次握手(建立连接)数据传输四次挥手(断开连接)TCP和UDP对比网络编程基础知识

  4. ruby - 在 Ruby 中将整数格式化为固定长度的字符串 - 2

    有没有一种简单的方法可以将给定的整数格式化为具有固定长度和前导零的字符串?#convertnumberstostringsoffixedlength3[1,12,123,1234].map{|e|???}=>["001","012","123","234"]我找到了解决方案,但也许还有更聪明的方法。format('%03d',e)[-3..-1] 最佳答案 如何使用%1000而不是进行字符串操作来获取最后三位数字?[1,12,123,1234].map{|e|format('%03d',e%1000)}更新:根据theTinMan的

  5. ruby-on-rails - 在 RSpec 中,如何以任意顺序期望具有不同参数的多条消息? - 2

    RSpec似乎按顺序匹配方法接收的消息。我不确定如何使以下代码工作:allow(a).toreceive(:f)expect(a).toreceive(:f).with(2)a.f(1)a.f(2)a.f(3)我问的原因是a.f的一些调用是由我的代码的上层控制的,所以我不能对这些方法调用添加期望。 最佳答案 RSpecspy是测试这种情况的一种方式。要监视一个方法,用allowstub,除了方法名称之外没有任何约束,调用该方法,然后expect确切的方法调用。例如:allow(a).toreceive(:f)a.f(2)a.f(1)

  6. ruby - 我正在学习编程并选择了 Ruby。我应该升级到 Ruby 1.9 吗? - 2

    我完全不是程序员,正在学习使用Ruby和Rails框架进行编程。我目前正在使用Ruby1.8.7和Rails3.0.3,但我想知道我是否应该升级到Ruby1.9,因为我真的没有任何升级的“遗留”成本。缺点是什么?我是否会遇到与普通gem的兼容性问题,或者甚至其他我不太了解甚至无法预料的问题? 最佳答案 你应该升级。不要坚持从1.8.7开始。如果您发现不支持1.9.2的gem,请避免使用它们(因为它们很可能不被维护)。如果您对gem是否兼容1.9.2有任何疑问,您可以在以下位置查看:http://www.railsplugins.or

  7. ruby - 如何以编程方式删除实例上的 "singleton information"以使其编码(marshal)? - 2

    我创建了一个由于“在运行时执行的单例元类定义”而无法编码的对象(这段代码的描述是否正确?)。这是通过以下代码执行的:#defineclassXthatmyusesingletonclassmetaprogrammingfeatures#throughcallofmethod:break_marshalling!classXdefbreak_marshalling!meta_class=class我该怎么做才能使对象编码正确?是否可以从对象instance_of_x的classX中“移除”单例组件?我真的需要一个建议,因为我们的一些对象需要通过Marshal.dump序列化机制进行缓存。

  8. Ruby 元编程问题 - 2

    我正在查看Ruby日志记录库Logging.logger方法并从sourceatgithub提出问题与这段代码有关:logger=::Logging::Logger.new(name)logger.add_appendersappenderlogger.additive=falseclass我知道类 最佳答案 这实际上删除了方法(当它实际被执行时)。这是确保close不会被调用两次的保障措施。看起来好像有嵌套的“class 关于Ruby元编程问题,我们在StackOverflow上找到一

  9. ruby - 按数字(从大到大)然后按字母(字母顺序)对对象集合进行排序 - 2

    我正在构建一个小部件来显示奥运会的奖牌数。我有一个“国家”对象的集合,其中每个对象都有一个“名称”属性,以及奖牌计数的“金”、“银”、“铜”。列表应该排序:1.首先是奖牌总数2.如果奖牌相同,按类型分割(金>银>铜,即2金>1金+1银)3.如果奖牌和类型相同,则按字母顺序子排序我正在用ruby​​做这件事,但我想语言并不重要。我确实找到了一个解决方案,但如果感觉必须有更优雅的方法来实现它。这是我做的:使用加权奖牌总数创建一个虚拟属性。因此,如果他们有2个金牌和1个银牌,加权总数将为“3.020100”。1金1银1铜为“3.010101”由于我们希望将奖牌数排序为最高的,因此列表按降序排

  10. ruby - Paperclip:以编程方式分配图像并设置其名称 - 2

    使用Paperclip,我想从这样的URL抓取图像:require'open-uri'user.photo=open(url)问题是我最后得到一个像“open-uri20110915-4852-1o7k5uw”这样的文件名。有什么方法可以更改user.photo上的文件名?作为一个额外的变化,Paperclip将我的文件存储在S3上,所以如果我可以在初始分配中设置我想要的文件名就更好了,这样图像就会上传到正确的S3key。像这样:user.photo=open(url),:filename=>URI.parse(url).path 最佳答案

随机推荐