SAS编程:Listings输出到RTF,如何实现内容换行?

关于Listing换行方面的内容,我总结有3个:

  1. Header内容的换行;
  2. 观测内容的换行;
  3. 输出列过多换行。

1.Header的换行

正常来讲,Listing中的列宽是根据输出列中具体的内容长度来确定的;如果列中内容较少,该列Header又不长的情况下,我们可以设置列宽完整地在一行中输出Header。但如果列中内容较少、Header过长,这时候就不宜设置较大列宽,应该在Header中手动设置换行,多行显示Header。Header的换行在Table中也经常出现。

常用的Header换行有两种方法,一种是PROC REPORT中的SPLIT=选项,指定分割字符进行换行;另一种是利用“转义字符+n”进行换行。

1.1 SPLIT= 选项

临床试验分析中,Lisiting内容一般都是通过PROC REPORT输出到RTF中,PROC REPORT中的SPLIT=选项可以指定Header的分割字符。PROC REPORT在到达分割字符时中断文本,并在下一行继续文本。不过要注意,分隔字符本身并不是列标题或文本值的一部分,但分隔字符的每次出现都计入标签的字符数,如果超过256个字符会发生截断。同时,该选项只适用于列标题分割,不会对列的内容进行分割。

我以SASHELP逻辑库中的CLASS数据集进行演示,将NAME、SEX两列输出到RTF文件中,列名分别设为“Name”、“Male Female”,SEX列名分两行输出。分隔符设置为#,为了测试列的内容是否换行,在Name列第一行的内容中补充“#A”。具体代码如下:

data class;
  set sashelp.class;
  if _n_ = 1 then name = strip(name)||"#A";
run;

ods rtf file = "../class.rtf";
proc report data = class split = "#";
  column name sex;
  define name /display "Name";
  define sex /display "Male#Female";
run;
ods rtf close;

处理后的CLASS数据集如下:


CLASS

RTF输出如下,列标题已经实现跨行,列的内容中的分隔符不会实现跨行:


RTF1

1.2 “转义字符+n”实现换行

先介绍一下转义字符(Escape character),我直接引用百度百科的内容:

转义字符是很多程序语言、数据格式和通信协议的形式文法的一部分。对于一个给定的字母表,一个转义字符的目的是开始一个字符序列,使得转义字符开头的该字符序列具有不同于该字符序列单独出现时的语义。因此转义字符开头的字符序列被叫做转义序列。

百科中也给了一些转义字符含义的示例:

转义字符示例

简单讲“转义字符+字母序列”可以表达某种特别的含义,不同的编程语言各自的转义字符可能不同。SAS是通过ODS ESCAPECHAR= 'escape-character';语句来定义转义字符的,转义字符应该是很少使用的字符,例如:@^,或#。对于RTF的转义字符有个注意点,\是一个特殊的RTF字符。因此,对于RTF输出,建议使用除\以外的转义字符。SAS中转义字符没有默认值,但我们可以像使用转义字符一样使用特殊转义序列(*ESC*)这与转义字符的作用是一致的

参考上面的转义字符的示例图,发现“转义字符+n”表示换行,我们可以由此实现Header的换行。我将转义字符设置为^,进行测试,代码如下:

data class;
  set sashelp.class;
  if _n_ = 1 then name = strip(name)||"^A";
run;

ods rtf file = "../class.rtf";
ods escapechar = "^";
proc report data = class;
  column name sex;
  define name /display "Name";
  define sex /display "Male^nFemale";
run;
ods rtf close;

数据集内容如下:


CLASS

输出的RTF如下:


RTF2

从RTF输出结果可以发现,^n实现了Header的换行。同时观察Name列第一行值,^A这个转义序列没有对应的特殊含义,转义字符不显示,字母A照常输出。

2. 观测内容的换行

在Listing的Output中,我们会经常遇到观测内容的换行,如果仅仅机械设置列宽,让内容自动换行,这样的换行大概率不尽人意。举个简单的例子,有关AE的Listing通常会这样输出时间变量,Ae Start Date (Day)。如果输出列较多,为了节省空间,Header中的(Day)通常换行显示,列中内容也通常将Date与Day换行显示。

如果靠不断调整列宽来实现(Day)的换行,这个工作会很繁琐。并且在自己设备上换行成功后,当使用其他设备打开输出文件时,由于软件的基本设置可能不同,这样的换行位置可能会发生变化。因此,不推荐通过调整列宽来实现列内容的换行。关于观测内容特定位置的换行,推荐1.2节的方法,使用“转义字符+n”实现。下面我代码演示一下:

data  ae;
  aestdtc = "2021-01-26";
  aestdy = 1;
  col1 = put(input(aestdtc,yymmdd10.), date9.)||"(*ESC*)n("||strip(put(aestdy,best.))||")";
run;

ods rtf file = "../ae.rtf";
proc report data = ae;
  column col1;
  define col1 /display center "Ae Start Date(*ESC*)n(Day)" style(column)=[cellwidth=1in] ;  
run;
ods rtf close;

数据集内容如下:


Dateset
RTF3

以上Header和列内容的换行使用的是特殊转义序列(*ESC*)n,我们也可以自定义转义字符进行实现(ods escapechar = "^"),Header的换行使用PROC REPORT的Split=的选项,结果与上面一致。

ods escapechar = "^";
data  ae;
  aestdtc = "2021-01-26";
  aestdy = 1;
  col1 = put(input(aestdtc,yymmdd10.), date9.)||"^n("||strip(put(aestdy,best.))||")";
run;

ods rtf file = "../ae.rtf";
proc report data = ae split="#";
  column col1;
  define col1 /display center "Ae Start Date#(Day)" style(column)=[cellwidth=1in] ;  
run;
ods rtf close;

3.输出列过多的换行

在做Lisiting时,输出列过多是一个常见的现象。不同的公司、不同的项目对于Listing过多列的处理要求可能不同。这里我介绍一下我接触到的两种方式,不管是哪种方式,一行放不下的列肯定是要换行输出的。换行输出会遇到一个问题,换行后查看后半部分列内容时,可能与前面内容不方便对应。这时候有两种处理办法,第一种,换行时在下一行也显示前一行的ID变量;第二种,在一页中只输出一个ID的记录,避免查看时产生误导。两种方式没有什么绝对的优劣,主要看在实际项目中的基于项目情况的选择。

下面我用SASHELP逻辑库中的CLASS数据集进行演示,我先多次拼接实现“列过多”,然后不做其他处理进行RTF输出。

data class;
  merge sashelp.class(rename=(sex=sex1  age=age1  height=height1  weight=weight1))
      sashelp.class(rename=(sex=sex2  age=age2  height=height2  weight=weight2))
      sashelp.class(rename=(sex=sex3  age=age3  height=height3  weight=weight3))
      sashelp.class(rename=(sex=sex4  age=age4  height=height4  weight=weight4));
  by name;
run;

ods rtf file = "../class.rtf";

proc report data = class;
  column name sex1-sex4  age1-age4  height1-height4   weight1-weight4;
run;

ods rtf close;

RTF输出结果如下:

RTF4

我们可以看到一页无法放下这个17个变量,最后4个变量自动进行换行输出;换行后由于记录数过多,还产生了跨页的情形。如果要在一页中查看某个人的后4个变量的信息,一下子很难对应到具体的那一条记录。现在,我用两种方式分别处理这种情况。

3.1 PROC REPORT中Define语句的ID选项

Define语句中的ID选项,会定义指定的变量为ID变量。ID变量及其左侧的所有列会显示在报表的每一页的左侧;当输出的列过多发生换行时,换行后最左侧会显示ID变量,便于识别输出内容。

考虑到换行后RTF不能放下所有记录,我们将数据集记录前10条输出到第一页,后面的记录输出到第二页。

前面自动换行后,第二部分只显示了4个变量,即便加上ID变量也只是5个变量,显得上下不协调。如果想要实现上下两部分显示宽度差不多的,一般通过调节各个变量的列宽,使得上部分变量列宽总和、下部分变量列宽综合以及页面展示宽度大致相等。这里就不进一步调整了,直接看下示例代码:

data class;
  merge sashelp.class(rename=(sex=sex1  age=age1  height=height1  weight=weight1))
      sashelp.class(rename=(sex=sex2  age=age2  height=height2  weight=weight2))
      sashelp.class(rename=(sex=sex3  age=age3  height=height3  weight=weight3))
      sashelp.class(rename=(sex=sex4  age=age4  height=height4  weight=weight4));
  by name;

  if _n_<=10 then pg=1;
  else pg = 2;
run;

ods rtf file = "../class.rtf";

proc report data = class;
  by pg;
  column name sex1-sex4  age1-age4  height1-height4   weight1-weight4;
  define name / id;
run;

ods rtf close;

第一页输出内容如下:


RTF5

我们可以看到换行后的第二部分左侧出现了ID变量Name,这样就方便查看了。

3.2 一页只输出一个ID的记录

如果没有使用ID选项,RTF一页输出多条记录,会造成查看不方便。但如果一页只生成一条记录,即便没有ID变量,也不会对查看造成误导,例如下面的情况:

data class;
  merge sashelp.class(rename=(sex=sex1  age=age1  height=height1  weight=weight1))
      sashelp.class(rename=(sex=sex2  age=age2  height=height2  weight=weight2))
      sashelp.class(rename=(sex=sex3  age=age3  height=height3  weight=weight3))
      sashelp.class(rename=(sex=sex4  age=age4  height=height4  weight=weight4));
  by name;
run;

ods rtf file = "../class.rtf";

proc report data = class;
  by name;
  column name sex1-sex4  age1-age4  height1-height4   weight1-weight4;
run;

ods rtf close;

输出的第一页结果如下:


RTF6

有人可能会疑问,RTF一页就输出这么点内容,页面看起来不会太“空旷”吗?有可能出现这种情况。但在实际项目中也经常有Listing的Title和Footnotes内容较多的情况,一个页面中实际的数据展示空间可能就只够一条记录的内容了。这时候一页输出一条记录,即便发生换行,也是可以接受的。

3.3 Define语句的Page选项的介绍

当Listing输出列过多时,如果不要求一条记录的所有列都在同一页中展示,我们还可以使用Page选项让多余的列进行换页输出。

Define语句中的Page选项,会在使用该选项的列之前,插入分页符。这样的话,这一列以及右侧的列都会在新的一页中输出。我对变量Height2进行该选项,Height2及其右侧变量都会换页输出,来看一下这个选项的效果:

data class;
  merge sashelp.class(rename=(sex=sex1  age=age1  height=height1  weight=weight1))
      sashelp.class(rename=(sex=sex2  age=age2  height=height2  weight=weight2))
      sashelp.class(rename=(sex=sex3  age=age3  height=height3  weight=weight3))
      sashelp.class(rename=(sex=sex4  age=age4  height=height4  weight=weight4));
  by name;
run;

ods rtf file = "../class.rtf";

proc report data = class;
  column name sex1-sex4  age1-age4  height1-height4   weight1-weight4;
  define name / id;
  define height2/ page;
run;

ods rtf close;

输出结果如下:


RTF7

以上就是有关Listing换行内容的分享,若有疑问,欢迎评论反馈!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容