数据分析技术丛书 深入解析SAS:数据处理、分析优化与商业应用 夏坤庄 等著 ISBN:978-7-111-48340-3 本书纸版由机械工业出版社于2015年出版,电子版由华章分社(北京华 章图文信息有限公司,北京奥维博世图书发行有限公司)全球范围内制 作与发行。 版权所有,侵权必究 客服热线:+ 86-10-68995265 客服信箱:service@bbbvip.com 官方网址:www.hzmedia.com.cn 新浪微博 @研发书局 腾讯微博 @yanfabook 目录 前言 Preface 第一篇 SAS编程和数据处理 第1章 Base SAS基础 1.1 SAS系统简介 1.2 启动SAS软件 1.3 SAS窗口环境 1.4 SAS文件和逻辑库 1.5 一个简单的SAS程序 1.6 SAS Studio 1.7 本章小结 第2章 读取外部数据到SAS数据集 2.1 SAS编程基本概念 2.2 通过DATA步读取数据 2.3 通过IMPORT过程读取外部文件数据 2.4 访问关系型数据库系统中的数据 2.5 SAS程序错误及处理 2.6 本章小结 第3章 对单个数据集的处理 3.1 选取部分变量 3.2 操作数据集的观测 3.3 创建新变量 3.4 循环和数组 3.5 SAS常用函数 3.6 将数据集写出到外部文件 3.7 本章小结 第4章 对多个数据集的处理 4.1 数据集的纵向串接 4.2 数据集的横向合并 4.3 数据集的更新 4.4 数据集的更改 4.5 数据集处理的一点补充 4.6 本章小结 第5章 数据汇总与展现 5.1 通过PRINT过程制作报表 5.2 通过TABULATE过程制作汇总报表 5.3 通过GPLOT过程制作图形 5.4 通过GCHART过程制作图形 5.5 ODS输出传送系统 5.6 本章小结 第6章 SAS SQL语言 6.1 SQL语言概述 6.2 使用SQL检索数据 6.3 使用SQL对表进行横向合并 6.4 使用SQL对表进行纵向合并 6.5 使用SQL管理表 6.6 本章小结 第7章 SAS宏语言 7.1 SAS宏语言概述 7.2 宏变量 7.3 宏函数 7.4 宏 7.5 宏语言与其他SAS语言 7.6 宏编程 7.7 本章小结 第8章 开发多语言支持的SAS程序 8.1 多语言支持的基本概念 8.2 NLS相关的SAS选项 8.3 NL格式和NL输入格式 8.4 字符串和字符处理函数 8.5 文本字符串外部化 8.6 本章小结 第二篇 SAS统计分析和时间序列预测 第9章 描述性统计分析 9.1 基本概念 9.2 描述性统计量 9.3 MEANS过程的补充 9.4 本章小结 第10章 参数估计与假设检验 10.1 参数估计 10.2 假设检验 10.3 非参数假设检验 10.4 分布拟合假设检验 10.5 本章小结 第11章 方差分析 11.1 方差分析的基本原理 11.2 单因素试验的方差分析 11.3 显著因素下的水平间差异检验 11.4 双因素试验的方差分析 11.5 本章小结 第12章 主成分分析与因子分析 12.1 主成分分析概述 12.2 使用SAS实现主成分分析 12.3 因子分析概述 12.4 使用SAS实现因子分析 12.5 本章小结 第13章 聚类分析 13.1 聚类分析的概述 13.2 划分法与层次法 13.3 本章小结 第14章 判别分析 14.1 判别分析概述 14.2 判别分析在SAS中的实现 14.3 本章小结 第15章 回归分析 15.1 变量关系探索 15.2 线性回归 15.3 自变量间的共线性诊断 15.4 本章小结 第16章 LOGISTIC回归分析 16.1 基本原理 16.2 运用LOGISTIC过程拟合模型 16.3 LOGISTIC过程的其他语句 16.4 建立模型 16.5 本章小结 第17章 时间序列分析 17.1 时间序列基本概念 17.2 平稳时间序列分析 17.3 趋势时间序列分析 17.4 季节时间序列模型 17.5 本章小结 第18章 SAS数据挖掘的一般流程 18.1 SAS数据挖掘概述 18.2 确定业务问题和数据准备 18.3 数据抽样、探索与加工 18.4 数据建模 18.5 本章小结 第三篇 SAS优化建模 第19章 运筹学概述 19.1 运筹学发展简介 19.2 优化模型的基本概念 19.3 优化模型的分类 19.4 优化建模步骤 19.5 SAS/OR简介 19.6 一个简单的OPTMODEL程序 19.7 本章小结 第20章 线性规划 20.1 数学模型 20.2 单纯形法 20.3 对偶理论和灵敏性分析 20.4 内点法 20.5 本章小结 第21章 运用PROC OPTMODEL建立线性规划模型 21.1 基本概念 21.2 基本结构 21.3 建立模型 21.4 读取SAS数据集 21.5 创建SAS数据集 21.6 本章小结 第22章 PROC OPTMODEL程序设计 22.1 PROC OPTMODEL中的流程控制方法与集合运算 22.2 模型的更新 22.3 网络流模型 22.4 本章小结 第23章 整数线性规划和混合整数线性规划 23.1 整数线性规划和混合整数线性规划概述 23.2 使用PROC OPTMODEL求解混合整数线性规划 23.3 使用0-1变量建模 23.4 本章小结 第24章 优化建模实例 24.1 集装箱问题 24.2 运输排程问题 24.3 本章小结 第四篇 SAS智能平台架构体系 第25章 SAS智能平台及行业解决方案 25.1 SAS智能平台 25.2 SAS商业智能 25.3 SAS数据管理和集成 25.4 SAS商业分析 25.5 SAS高性能分析 25.6 本章小结 第26章 SAS应用的架构规划 26.1 SAS应用的架构规划 26.2 SAS应用的I/O系统规划 26.3 本章小结 第27章 SAS智能平台安全管理 27.1 身份标识 27.2 认证 27.3 授权 27.4 加密 27.5 安全性审计 27.6 本章小结 第28章 SAS智能平台的高可用性 28.1 高可用性相关概念 28.2 SAS高可用性方法概述 28.3 SAS元数据服务器 28.4 SAS计算层 28.5 SAS中间层 28.6 数据层 28.7 本章小结 前言 为什么要写这本书 数据和模型描述着世界,而SAS恰恰就是关于数据和模型的技术。 SAS技术在全球的数据处理和分析领域举足轻重。在国内,SAS的应用 日趋广泛,自然,对掌握SAS技术的人才需求也日益旺盛。 但是当大家谈及SAS的时候,普遍的一个感受是,掌握SAS比较 难。这使我记起在2000年刚刚加入SAS中国公司几天后的一个下午,时 任SAS中国区技术总监的栾世武博士问我:“怎么样?SAS难学吗?”其 实,在SAS公司的同事当中,大家并不会认为SAS有多难。究其原因, 不过是如下几个: ·在SAS公司,有着明确的路线图,大家可以清楚地知道学习SAS某 个领域的顺序和步骤是什么。对于系统性非常强而且知识范围又较广的 SAS而言,这是很重要的。 ·对于路线图中的每一个阶段,SAS公司都提供了详尽的资料供阅读 和学习。 ·有实际的项目去实践和锻炼。 上面所提到的因素,也正是大部分期望学习SAS技术的从业者快速 有效掌握SAS的“窍门”。基于这样的经历和思考,几年以来我一直在构 思这样一本书: 1)以书中的章节结构来体现学习SAS核心内容的路线图。 2)在每个章节的内容中,包含路线图中对应部分的必要学习资 料,并且使得读者在读完相应的内容之后,有能力并且了解如何去学习 更加深入和广泛的知识。 3)提供贴近实际应用项目甚至有些复杂的例子,让读者领会解决 实际问题的思路和技巧。 本书就是基于上述构思的一个实现,希望能够帮助大家系统地掌握 SAS的专业知识,进而从容地将其应用于商业实际中。 读者对象 本书主要适合于以下读者: ·使用SAS进行数据抽取、转换和清洗的技术人员。 ·需要使用SAS对数据进行深入分析和数据挖掘的分析人员。 ·需要使用SAS进行时间序列预测和优化决策的建模专家。 ·使用SAS进行项目规划、实施和管理的系统架构师、系统管理员和 项目管理人员。 ·团队的工作涉及SAS产品与技术的管理人员。 如何阅读本书 本书共4篇,系统介绍了SAS的核心技术模块和架构体系。 第一篇介绍SAS编程和数据处理(第1~8章)。内容包括如何运用 SAS进行数据读入、处理和展现。掌握本书的这一篇内容可以满足大部 分实际项目中数据处理的需要。该篇建议刚刚接触SAS的读者仔细研 读,对SAS编程有全面了解的读者可以快速浏览或者在需要时查阅。 第二篇介绍SAS统计分析和时间序列预测(第9~18章)。内容既包 括基本的理论介绍,又包括如何利用SAS去实现的具体技术。该篇建议 需要学习数据分析、数据挖掘或进行预测的读者仔细阅读。 第三篇介绍SAS优化建模(第19~24章)。对于从事优化的读者来 说,这一篇的内容将很有帮助。这一篇对常见的优化问题做了全面的介 绍。其中的用例非常贴近现实,建议读者仔细研读。此外,建议从事优 化的读者也学习一下第二篇中第17章关于时间序列分析的内容,因为在 实际优化工作中,经常需要预测。 第四篇介绍SAS智能平台架构体系(第25~28章)。对于该篇内 容,不需要像前3篇一样有深入的掌握,但这些内容对于项目规划和架 构设计人员设计一个满足安全性、高可用性和高性能的SAS应用会非常 有帮助。 致谢 本书的完成是整个写作团队合作的成果,蕴含着每一个作者的努 力。 在本书即将完成之际,需要感谢的名字很多,把这长长的列表沉在 深处之后,在此感谢我们所处的时代和我所在的SAS公司。时代赋予了 企业和个人对数据进行分析和建模的需求,SAS公司给予了我们完成本 书所需要的知识和使命感。 特别感谢机械工业出版社华章公司的Lisa Yang。感谢Lisa的热情相 邀和宝贵建议,促成了本书的完成,她的专业而高效的审阅,也使得本 书增色极多。 夏坤庄(Kansun Xia) 北京,2014年7月 Preface Why to write this book The world can be described with data and model,and SAS is exactly about these two.SAS has been proved to be a prominent player in data management and advanced analytics field worldwide.In China,SAS is also getting widely used in more and more industries,as a result,talent demands of SAS expertise is growing as well. However,when speaking of SAS,the first impression is that it is difficult to master.In an afternoon in 2000,not long after I joined SAS, Dr.Luan Shiwu,who was the CTO of SAS China,asked me,“How do you feel about SAS?Is it difficult to learn?”Actually,like the majority of my colleagues,I do not think SAS is difficult to learn.Here are some reasons: ·SAS provides an explicit roadmap allowing the staff to learn specific domains of SAS in right order step by step.That is very helpful since SAS technology is highly systematically and broadly in scope of knowledge. ·Within each phase of the roadmap,elaborate materials are provided. ·Projects are available for practice and exercise. I believe that the above reasons I mentioned are essential to anyone who wants to learn SAS.Basing on such experience and consideration,I have been conceiving of a book with features below: ·Implement the roadmap of learning SAS core knowledge by organizing chapters of this book. ·Provide essential learning materials corresponding to each phase of the roadmap in each chapter,so that readers are able to learn more deeply and widely after finishing each part. ·Provide examples that were extracted from business projects for readers so that they can learn approaches and skills of solving complicated business problems. With this book,I hope it can help you master SAS technology systematically,further more you can apply it easily into your professional work. Target audience This book is mainly for the following people: ·Practitioners who use SAS to extract,transform and load data. ·Analysts who take advantage of SAS to do statistical analysis and data mining. ·Modeling experts who utilize SAS for time series forecasting and optimization. ·System architects,system administrators and project managers who use SAS to plan,implement,and manage projects. ·Managers whose team’s work involves SAS product and technology. How to read this book This book consists of four parts,which systematically introduces SAS technology and architecture. Part One(Chapter 1-8)is about SAS programming which covers how to use SAS to import,process and present data.After finishing this part, you can handle most of data processing work.It is suggested for new learners to read this part thoroughly,while for experienced users,you can quickly look through or refer to as it when needed. Part Two(Chapter 9-18)talks about SAS statistical analysis and time series forecasting,including both fundamentals and specific technology for how to use SAS for implementation.People who need to learn data analysis, data mining or forecasting are recommended to read this part. Part Three(Chapter 19-24)is about operational research.It is very useful for those who work on optimization modeling.This part gives fully description on common optimization problems with practical examples.In addition,people who work on optimization are suggested to learn Chapter 17of Part Two,which is about time series forecasting,as forecasting usually goes along with optimization in actual work. Part Four(Chapter 25-28)is about SAS platform architecture.This part is independent from the previous three parts.Project planners and architects will find this part very useful when they design a SAS application with high security,high availability,and high performance. Contents Part One(Chapter 1-8):SAS Programming and Data Processing Chapter 1 Foundation of Base SAS Chapter 2 Reading External Data to SAS Data Set Chapter 3 SAS Data Set Processing Chapter 4 Multiple Data Sets Processing Chapter 5 Data Summary and Presentation Chapter 6 SAS SQL Language Chapter 7 SAS Macro Language Chapter 8 SAS Programming with National Support Part Two(Chapter 9-18):SAS Statistical Analysis and Time Series Forecasting Chapter 9 Descriptive Statistical Analysis Chapter 10 Parameter Estimation and Hypothesis Test Chapter 11 Analysis of Variance Chapter 12 Principal Component Analysis and Factor Analysis Chapter 13 Cluster Analysis Chapter 14 Discriminant Analysis Chapter 15 Regression Analysis Chapter 16 LOGISTIC Regression Analysis Chapter 17 Time Series Analysis Chapter 18 General process flow of SAS Data Mining Part Three(Chapter 19-24):SAS Optimization Modeling Chapter 19 Overview of Operational Research Chapter 20 Fundamentals of Linear Programming Chapter 21 Linear Programming with PROC OPTMODEL Chapter 22 PROC OPTMODEL Programming Chapter 23 Integer Linear Programming and Mixed Integer Linear Programming Chapter 24 Examples of Optimization Modeling Part Four(Chapter 25-28):SAS Business Implementation Chapter 25 SAS Intelligence Platform and Solutions Chapter 26 SAS Application Infrastructure Planning Chapter 27 Security Administration of SAS Intelligence Platform Chapter 28 High Availability of SAS Intelligence Platform Acknowledgement This book is the outcome of team work,including each writer’s contributions. With the completion of this book,there are too many people to whom I would like to express my appreciation.I would like to thank to this era and SAS.It is this era that endows organizations and individuals with the need of data analysis and modeling,and it is SAS that offers me the knowledge and the feeling of responsibility to complete this book. In the end,I’m particularly grateful to Lisa Yang from Huazhang Company of China Machine Press for her warm invitation and precious suggestions which contribute to the completion of this book.Her professional and efficient review work improved this book a lot. Kansun Xia Beijing,China July,2014 第一篇 SAS编程和数据处理 第1章 Base SAS基础 第2章 读取外部数据到SAS数据集 第3章 对单个数据集的处理 第4章 对多个数据集的处理 第5章 数据汇总与展现 第6章 SAS SQL语言 第7章 SAS宏语言 第8章 开发多语言支持的SAS程序 第1章 Base SAS基础 本章将从SAS系统开始,介绍Base SAS的组成部分,并以Windows 环境为例介绍SAS窗口环境、SAS逻辑库、数据集、目录(Catalog)等 SAS中常用的概念。在了解了这些基础知识之后,会引导读者使用以上 的知识编写一段简单代码,提交执行,并查看日志及运行结果。最后将 用简短的篇幅简单介绍SAS最新推出但将会承担重要角色的SAS Studio 的基本功能。 需要注意的是,本书中描述的内容会包括Windows和UNIX(和 Linux)操作系统,如果在Windows和UNIX环境下的操作或命令有所不 同,将会专门说明。本书内容未专门考虑Mainframe,因为其操作使用 模式相差很多,而且读者会较少接触和使用Mainframe环境,但书中对 SAS软件和产品的描述、编程概念和程序语言以及给出的代码在 Mainframe环境下同样适用。关于SAS的版本,本书是基于写作时发布 的最新版SAS 9.4来展开的,除非特别说明,书中内容也同样适用于较 早的版本SAS 9.3和SAS 9.2。 本章对Base SAS窗口环境进行了着重介绍,目的在于让读者学会如 何使用SAS窗口环境开发、运行SAS代码,并查看结果和检查代码运行 日志。但是书中不会介绍每个菜单、子菜单、工具栏以及其他在Base SAS软件中出现的元素和功能,因为读者在实际学习和工作中可以很方 便地通过SAS软件提供的帮助文件进行了解。 1.1 SAS系统简介 SAS提供了一套集成的可扩展的解决方案和使用灵活、功能强大的 SAS编程语言,用于执行如下任务:数据输入和获取、数据转换处理和 管理、报表绘制和图形、统计和数学分析、商业规划、预测、运筹优 化,以及应用开发等。 SAS可以在多种操作系统下运行,包括Windows、UNIX、Linux以 及Mainframe等。同时,SAS程序代码具有很好的移植性,在一种环境 下开发的SAS代码可以在其他操作系统下运行。 SAS系统的核心Base SAS由以下部分组成。 ·DATA步:用于处理和管理数据。 ·SAS过程(Procedure):用于分析、处理和制作报表。 ·可扩展和定制SAS软件程序的宏语言(Macro Facility):可以减少 程序文本,使SAS程序编写得更有效且易于维护,便于编写更为复杂的 程序逻辑。 ·DATA步调试器:当提交的DATA步运行出错或产生的输出结果与 预期不一致时,可以借助它来跟踪DATA步的执行情况,从而帮助发现 程序逻辑中的错误。 ·输出交付系统(Output Delivery System,ODS):该系统会产生各 种易于访问的格式输出,例如,HTML文件、传统的列表输出、 PostScript文件、RTF文件和输出数据集等。 ·SAS窗口环境:它是一个开发和测试SAS程序的交互式图形用户界 面,本节后面会有更进一步的介绍。 这其中,前面3个是SAS语言的主要元素,本篇后面的章节会专门 介绍。 Base SAS软件提供数据处理过程和基础的统计过程FREQ、 MEAN、CORR及UNIVARIATE等,可以与其他的SAS产品一起使用, 从而实现更强大的数据读取、分析、优化、展示等功能。下面列出了部 分常用的SAS产品,用于实现数据读取、统计分析、优化和信息展示等 功能。 (1)SAS/ACCESS接口 提供与各种第三方数据源进行交互的功能。例如各种关系型数据 库,诸如Oracle、DB2、Teradata等;ERP系统诸如SAP R/3、PeopleSoft 等;同样对于Hadoop等也有专门的ACCESS接口。对于不同的数据源, ACCESS接口需要单独的软件使用许可。SAS与第三方的数据源进行交 互时,将直接调用该数据库或应用厂商提供的客户端对数据进行访问, 从而保证了与数据访问的效率。此外,SAS/ACCESS还提供接口访问 Microsoft Access数据库文件和Excel工作簿文件中的数据。 (2)SAS/GRAPH SAS/GRAPH是SAS系统的数据可视化和展现(图形)组件,用于 数据和信息展现,并且它可通过二维和三维图形(包括图表、散点图和 地图),可视化地展现数据值之间的关系。还可创建文本幻灯片、生成 各种图形输出,并可提供实用程序和管理输出。 (3)SAS/STAT SAS/STAT软件提供了全面的统计分析方法,共有超过75个统计分 析过程,包括T检验、方差分析(ANOVA过程)、聚类分析 (CLUSTER过程、VARCLUSTER和FASTCLUS过程)、因子分析 (FACTOR过程)、回归分析(REG过程)、逻辑斯蒂(LOGISTIC过 程)等。SAS/STAT软件还包括效能和样品容量分析(PSS)应用程 序。该软件不断被更新,以反映新的研究成果和方法。 (4)SAS/ETS 提供用于经济计量分析、时间序列分析和预测(ESM过程、 ARIMA过程和UCM过程等)、系统建模与仿真(MODEL过程)、离散 选择分析、定性有限因变量模型分析、时间序列数据的季节性调整、财 务分析和报告、访问经济和金融数据库及时间序列数据的管理。除了以 上过程外,SAS/ETS软件还包括对经济和金融数据库以及互动环境的无 缝访问,从而进行时间序列预测及投资分析。 (5)SAS/OR SAS/OR专注于运筹与优化。SAS/OR提供的OPTMODEL建模语言 用于构建、解决和维护最优化模型的建模环境,通过OPTMODEL过程 的各种求解器或单个过程,例如OPTLP、OPTMILP、OPTMILP过程, 解决线性规划、混合整数规划、非线性规划等问题。 以Base SAS软件和以上产品与技术作为基础,构建在SAS智能平台 (SAS Intelligence Platform)上的SAS许多商业解决方案,可以帮助各 类商业客户和其他组织机构解决诸多业务领域的特定问题,例如客户智 能、风险管理、供应链、零售等。关于SAS商业解决方案的内容,在本 书的第四篇会有相应的介绍。 启动SAS软件 1.2 SAS有多种运行模式:SAS窗口环境模式、非交互式模式、批处理 模式及交互式行模式,下面会一一介绍。除了上面提到的4种模式外, SAS还可运行在对象服务器模式里,SAS元数据服务器、工作区服务 器、存储过程服务器和OLAP服务器都是属于这种模式。关于这些服务 器,在本书第四篇会进行讨论。 1.2.1 SAS窗口环境模式 SAS窗口环境是SAS提供的一种交互式图形界面,是在Windows环 境下使用SAS编辑或提交SAS程序语句最方便也是最常用的模式。在 SAS窗口环境中,用户可以通过程序编辑器编辑并提交SAS语句,程序 语句的执行状态、执行时间等日志信息及put语句的输出会显示在日志 窗口,同时还会提供在线帮助等。本章下一节会使用Windows环境下的 窗口环境作为示例,详细介绍SAS窗口环境的各个窗口功能及其使用。 在Windows环境下启动SAS窗口环境和启动其他Windows应用程序 一样有多种方式,可通过“开始”菜单里的快捷方式、命令行等方式进 行。在安装SAS软件时,SAS软件安装程序会提示选择要安装的SAS语 言版本。如果当前操作环境下安装了多种语言的SAS,英文的SAS可以 通过“开始” “程序” SASSAS 9.4(English)启动。启动所有语言(包 括英文)的SAS软件时,其快捷方式位于“开始” “程序” SA SAdditional Languages中。例如,启动Windows操作环境下简体中文SAS 软件的快捷方式为:“开始” “程序” SASAdditional LanguagesSAS 9.4(Chinese(Simplified)),如图1.1所示。 图1.1 启动简体中文SAS窗口环境 此外,还可以使用命令行方式启动SAS窗口环境。在下面给出的 Windows和UNIX操作环境下的命令后,都可以指定其他系统选项来定 制要启动的SAS会话。例如,选项-NODATE表示在该SAS会话中产生的 输出页面里不显示日期,选项-CONFIG指定SAS配置文件,以在启动时 加载配置文件中更多的系统选项等。 ·Windows环境 C:\>"C:\Program Files\SASHome\SASFoundation\9.4\sas.exe" ·UNIX环境 #/opt/SASHome/SASFoundation/9.4/sas -dms UNIX环境下的命令行若不加选项-DMS,则会进入SAS的显示管理 系统。当使用Windows机器通过Telnet远程登录SAS软件所在的UNIX主 机时,如果需要使用SAS窗口环境,可以在该Windows机器上启动XWindows软件,例如Exceed、XMing、Cygwin等,并设置当前Telnet会 话的DISPLAY环境变量到该Windows机器上。这样,所启动的SAS窗口 环境会重定向到该Windows操作系统。当启动SAS的显示管理系统时, 在该Windows环境下会弹出类似的SAS窗口环境。在初次使用XWindows窗口时会有些不习惯,有些操作与Windows环境下的SAS窗口 稍有差异,但大部分都很类似。 在UNIX环境下,更多使用的是非交互模式或批处理模式,或者其 他的工具。例如,可使用Windows环境下的客户端程序SAS Enterprise Guide将SAS代码提交到UNIX服务器上。 1.2.2 非交互模式 很多情况下,SAS程序保存在外部操作系统文件中,这时可以在不 启动SAS窗口环境的情况下非交互式地提交该文件。使用非交互模式执 行保存在外部文件中的SAS程序语句时,SAS会打开该文件,执行该文 件中的程序,并将日志和输出根据操作环境写入指定文件中。当该文件 中的SAS程序执行完成时,SAS自动退出。下面是非交互模式下使用 SAS执行外部文件中的SAS程序语句示例。 ·在Windows环境下的示例如下: C:\>"C:\Program Files\SASHome\SASFoundation\9.4\sas.exe" -sysin C:\sas\code\test.sas –log C:\sas\logs\test.log -print C:\sas\lst/test.lst ·在UNIX环境下的示例如下: #/opt/SASHome/SASFoundation/9.4/sas –sysin /opt/sas/code/test.sas –log /opt/sas /logs/test.log -print /opt/sas/lst/test.lst 选项“-sysin”指定SAS程序语句所在的文件。当要执行的SAS程序语 句文件紧接着sas命令时,该选项可省略。“-log”指定输出日志文件,“print”指定执行输出文件。 1.2.3 批处理模式 在支持批处理或后台执行的操作环境下,SAS还可运行在批处理模 式中。用户可以将上面的一个或多个非交互模式中的命令写入批处理脚 本,并保存在批处理.bat(Windows环境下)或.sh(UNIX环境)文件 中,然后提交该批处理文件执行。当以批处理模式提交SAS作业时,会 生成两个文件,它们分别包含该作业执行的SAS日志和输出。 下面给出UNIX下的.sh文件内容,该批处理文件提交了两个SAS程 序执行test.sas和test2.sas。Windows环境下类似,但必须使用相应的脚本 语法。 #!/bin/sh cd /opt/sas /opt/SASHome/SASFoundation/9.4/sas /opt/sas/code/test.sas -log /opt/sas /logs/test.log -print /opt/sas/lst/test.lst & wait Sleep 15 /opt/SASHome/SASFoundation/9.4/sas /opt/sas/code/test2.sas -log /opt/sas /logs/test2.log -print /opt/sas/lst/test2.lst & wait 当使用调度软件或操作系统调度命令对SAS作业进行预定执行时, 通常使用该模式。这样可以让执行时间较长的SAS作业在晚间或其他预 定时间执行,或根据业务需要定期自动执行。下面给出了在Windows和 UNIX环境下使用操作系统计划或预定功能批处理执行的示例。在SAS 商业智能解决方案中这种模式也经常使用,SAS智能平台也提供了与第 三方调度软件和操作系统调度服务的集成。 1.Windows环境 使用Windows提供的任务计划程序,依次选择“开始” “附件” “系统工具” “所有程序” “任务计划程序”,并指定任务要执行的操作脚 本为上面提到的批处理文件。如图1.2所示为在Windows“任务计划程 序”中预定为在每天00:30:00执行的SAS作业,所设定操作的启动程 序为包含SAS命令的.bat文件。 2.UNIX环境 使用crontab命令预定上面提到的批处理文件。下面的crontab文件内 容给出了预定在每天凌晨00:30:00执行批处理文件sasjob.sh中的作 业。 30 00 * * * /bin/ksh /opt/sas/scheduler/sasjob.sh > /opt/sas/scheduler/sasjob.log 2>&1 图1.2 1.2.4 Windows环境下的“任务计划程序”窗口 交互式行模式 该模式在UNIX操作系统中可用,是一种较少使用的模式。但作为 SAS支持的启动模式,在这里也简单地介绍一下。在交互行模式下,顺 序地输入程序语句,所输入的DATA步或PROC步当遇到RUN、QUIT、 分号、另一个DATA步或PROC步,或者ENDSAS语句时会提交执行。 同时随着DATA步或PROC步的提交,这些程序语句的日志和输出(如 果有输出的)也会立即显示。可使用NODMS或NODMSEXP系统选项启 动交互式行命令模式的SAS会话。使用NODMS选项的命令示例如下: #/opt/SASHome/SASFoundation/9.4/sas –nodms 后面还可以接其他系统选项或使用-CONFIG选项指定SAS配置文 件。按EOF键(Ctrl+D组合键)或提交ENDSAS语句将结束交互式行命 令模式的SAS会话。示例如下: 5? endsas; 1.2.5 配置文件和AUTOEXEC文件 使用上述任何一种模式启动SAS时,都可以通过定义配置文件和 AUTOEXEC文件来定制SAS会话。在这两个文件中可以指定系统选项 和任何时候启动SAS会话时自动执行的SAS语句。SAS系统选项控制 SAS会话的许多方面,包括输出目的地、程序执行效率及SAS文件和逻 辑库的属性等。 SAS配置文件的名称为sasv9.cfg,AUTOEXEC文件的名称为 auoexec.sas,这些文件通常都位于SAS的安装根目录下。在多语言安装 环境中,SAS根目录下的nls目录中还包括各种语言适用的SAS配置文件 所在目录,例如en是英文、zh是简体中文、zt是繁体中文。启动SAS时 如果不指定配置文件,在Windows操作环境下,SAS会去查找SAS安装 根目录下的sasv9.cfg文件。通过Windows“开始”菜单启动各种语言的 SAS软件时,SAS会自动调用对应语言的配置文件。而在UNIX操作环境 中,SAS启动时所使用的配置文件是在启动SAS的文件中指定的。 有时,也会需要定制SAS启动时的配置文件。一个比较实际的例子 就是,在很多商业项目中,不希望SAS使用其默认的临时逻辑库(逻辑 库的知识会在后面介绍)WORK,而是希望将临时逻辑库WORK建立在 一个高速存储系统中,以提高SAS对某些常用操作的效率,这时就可以 在配置文件中指定临时WORK的物理位置。 在Windows环境下使用定制的配置文件sasv9_custom.cfg的示例如 下。在该示例中,假定该文件位于目录C:\Program Files\SASHome\SASFoundation\9.4下(在UNIX环境下类似)。 C:\> "C:\Program Files\SASHome\SASFoundation\9.4\sas.exe" -config "C:\Program Files\SASHome\SASFoundation\9.4\sasv9_custom.cfg" 系统选项AUTOEXEC用于指定AUTOEXEC文件。AUTOEXEC文件 包含启动SAS或其他SAS进程时自动执行的SAS语句,例如该文件可包 含一些定义在SAS会话中的经常使用的SAS逻辑库的LIBNAME语句。 SAS启动时,如果没有指定AUTOEXEC或NOAUTOEXEC选项, SAS会在当前目录、用户目录和SAS安装根目录下查找AUTOEXEC文 件。可以在启动SAS的命令行里指定AUTOEXEC选项,还可以通过 SASV9_OPTIONS环境变量将该选项放入配置文件里。 在Windows环境下通过命令行指定autoexec文件autoexec_custom.sas 的示例如下。在该示例中,假定该文件位于目录C:\Program Files\SASHome\SASFoundation\9.4下(在UNIX环境下类似)。 C:\> "C:\Program Files\SASHome\SASFoundation\9.4\sas.exe" -AUTOEXEC "C:\Program Files\SASHome\SASFoundation\9.4\autoexec_custom.sas" 1.3 SAS窗口环境 SAS窗口环境是一个开发、调试和运行SAS程序的交互式图形用户 界面。通过SAS窗口环境,用户可以交互式地编辑和执行SAS代码、显 示SAS日志、查看SAS过程的输出以及在线帮助,同时还可以通过图形 界面操作数据和改变SAS系统设置。SAS窗口环境通常在Windows系统 下使用,所以本书后面的章节都将以Windows环境下的SAS窗口环境进 行说明。 SAS软件启动后的界面包括菜单、命令框、工具栏、窗口、窗口条 以及状态栏,同时还支持浮动菜单、Windows环境快捷键(比如粘贴快 捷键Ctrl+C及剪贴板功能),以及Base SAS软件本身提供的快捷键(比 如,提交代码执行“F3”)。 SAS菜单包括在当前上下文环境下可选择的选项列表,当正在使用 的窗口发生变化时,菜单项会随之发生变化。例如,如果当前窗口 是“资源管理器”,那么菜单视图会显示在“资源管理器”窗口可用的视图 选项。如果“程序编辑器”是当前窗口,那么菜单视图会显示在“程序编 辑器”窗口可用的视图选项。“工具栏”则显示为窗口按钮或图标。当单 击“工具栏”里的工具项时,会产生对应的功能或动作。例如,单击“工 具栏”里打印机的图标会开始打印过程。同样工具栏中的可选工具项也 和当前的活动窗口相关。命令框位于工具栏左侧。可以在命令框输入命 令行,例如打开SAS窗口和获取帮助信息。 下面来介绍SAS窗口环境的6个主要窗口:“程序编辑器”、“日 志”、“输出”、“结果”、“SAS资源管理器”和“编辑器”。第一次启动时, 默认打开的窗口为“程序编辑器”、“日志”、“输出”和“SAS资源管理 器”窗口,“输出”窗口隐藏在其他窗口后面。所打开的窗口和窗口布局 与SAS所在的操作环境相关,例如,在Windows环境下,“增强型编辑 器”会代替“程序编辑器”。如图1.3所示为Windows环境下Base SAS软件 的窗口环境,其中“结果”窗口和“SAS资源管理器”共用窗口,可通过窗 口下端的选项卡进行切换。提交SAS程序执行完成后,默认的HTML输 出会展示在“结果”窗口中。 图1.3 Windows环境下的SAS窗口环境 在任一时间,将只有一个窗口处于激活状态,该窗口称为当前窗口 或活动窗口,可以通过单击“窗口条”的窗口标签激活对应窗口。在命令 栏输入相应命令并按回车键,或在菜单视图的子菜单中也可以打开并激 活对应的窗口。 1.3.1 SAS资源管理器 “SAS资源管理器”(Explorer)窗口用于管理该窗口环境中的文 件,包括查看SAS文件列表、创建新的SAS文件,查看、添加或删除逻 辑库,创建外部文件的快捷方式,移动、复制和删除文件,打开相关的 窗口(比如新建逻辑库窗口)等。该窗口最常用的功能是管理逻辑库及 逻辑库中的SAS文件,相关内容将在1.4节介绍。 “SAS资源管理器”以树状结构管理当前SAS环境中的文件,最上层 显示的图标为“逻辑库”、“文件快捷方式”、“收藏夹”和“计算机”,如图 1.3所示。可以通过双击每个图标进入其下层的内容或打开一个文件。 如果当前不在最上层,可以通过菜单“视图” 工具项 向上一级,或工具栏上的 ,返回至上一级。还可通过菜单“视图” 显示树状结构打开 两级窗口。 “SAS资源管理器”窗口可通过在命令框中输入EXPOLORE并按回车 键来打开,或者选择菜单“视图” 1.3.2 “SAS资源管理器”打开。 程序编辑器 “程序编辑器”(Program Editor)窗口用于输入、编辑、提交和保存 SAS程序。该窗口还可通过在命令框输入PROGRAM或PGM并按回车键 来打开,或者选择菜单“视图” “程序编辑器”打开。默认设置下, 在“程序编辑器”窗口中,代码提交后就会被清除,可在命令框中输入 recall重新显示。可通过修改如下设置来取消清除代码:在当“程序编辑 器”窗口为活动窗口时,选择菜单“工具” “选项” “程序编辑器”打开 的“程序编辑器选项”对话框的“编辑”选项卡,取消勾选“提交时清除文 本”选项。此外,在该对话框中还可以修改其他设置。 在Windows环境下,默认打开的“程序编辑器”窗口为“增强型编辑 器”。“增强型编辑器”具有更加丰富的功能,可折叠和展开代码段,还 可以通过设置使其在窗口左侧边缘显示行号。可通过在命令框输入 wnextedit或wpgm并按回车键来打开或切换“增强型编辑器”,或者选择 菜单“视图” “增强型编辑器”打开。 在编辑器中输入的SAS程序代码可保存到文件系统的文件中。 1.3.3 日志 “日志”(Log)窗口可查看当前SAS会话和SAS程序的消息。如果提 交的程序产生意外结果,日志消息会提示错误信息,可以帮助找出SAS 程序或设置的错误。如果SAS程序中有PUT语句,那么该输出默认会写 到SAS日志中。在命令框输入LOG并按回车键,或选择菜单“视图” “日志”,可打开“日志”窗口。日志同样也可保存到文件系统中,以便于 以后查看。作为SAS开发或使用人员,要养成每次代码提交执行完成后 首先检查SAS日志的习惯。 1.3.4 结果 通过“结果”(Results)窗口可查看在该窗口环境提交的SAS代码的 输出列表。在SAS 9.4中,默认输出为HTML格式。“结果”窗口以树形结 构列出SAS程序代码执行后产生的输出。提交SAS代码后,HTML内容 显示在“结果浏览器”窗口,文件名称展示在“结果”窗口中。可以查看、 保存或打印单个结果文件。 在命令栏输入ODSRESULTS并按回车键,或者选择菜单“视图” “结果”,可打开“结果”窗口。 1.3.5 输出 可通过“输出”(Output)窗口查看SAS程序的列表(LISTING)输 出。默认情况下,“输出”窗口位于其他窗口后面。当SAS程序产生了列 表输出时,“输出”窗口会自动移动到显示前面。可在命令栏输入 OUTPUT、OUT、LISTING或LST并按回车键来打开“输出”窗口,或者 选择菜单“视图” “输出”打开。 从SAS 9.3开始,SAS的默认输出从列表输出变成了HTML。可以通 过ODS语句打开列表输出,产生列表输出的同时也会生成HTML,不再 需要列表输出时可再使用相应的ODS语句关闭该类型输出。还可使用菜 单“工具” “选项” “参数选择”对话框的“结果”选项卡,选择输出类型 和设置系统参数,参数选择对话框的默认设置如图1.4所示。勾选“创建 列表”复选框会打开SAS软件的列表输出,还可选择HTML的样式,默认 为HTMLBlue。 图1.4 结果参数设置 1.4 SAS文件和逻辑库 在熟悉了SAS窗口环境后,接下来了解一下SAS文件和SAS管理文 件的方式。SAS文件是指由SAS创建、维护和管理,并且SAS知道其结 构的文件,例如SAS数据集、目录(Catalog)等。通常这些文件也表现 为操作环境中的文件,操作环境也会对它们进行管理。所有的SAS文件 都存在于SAS逻辑库中。SAS逻辑库用于组织、查找和管理SAS文件。 在SAS中,通过该文件所在逻辑库及文件名来使用SAS文件。 SAS数据集由SAS创建和管理,是SAS存储和处理数据的主要方 式。根据其文件是否包含数据值分为SAS数据文件和SAS视图。SAS数 据文件和SAS视图可以简单理解为与我们经常使用的数据库管理系统中 的表和视图。关于SAS逻辑库和数据集将在第2章进行更详细的讲解。 SAS目录(Catalog)是一种特殊的SAS文件,以目录项的形式存储 多种不同类型信息。一个SAS目录可包含多种类型的目录项,这些目录 项包含系统信息(例如功能键定义)和应用程序信息(例如窗口定义、 帮助窗口、格式(Format)、读入格式(Informat)、宏(Macro)或图 形输出)。 下面通过“SAS资源管理器”窗口浏览SAS逻辑库及SAS文件。启动 SAS窗口环境,在“SAS资源管理器”窗口双击“逻辑库” sashelp。如图 1.5所示的界面给出了当前环境下SAS系统中的逻辑库:Maps、 Mapsfgk、Mapssas、Sashelp、Sasuser和Work。逻辑库Sashelp中的SAS 文件如图1.6所示, 图标为数据集, 图标为SAS目录。SAS目录还可 打开显示更小的单元目录项。 图1.5 SAS逻辑库 图1.6 SAS数据集和目录 SAS除了可以处理SAS文件外,还可以处理外部文件和数据库管理 系统(Database Management System,DBMS)文件。SAS处理的外部文 件指由操作系统管理和维护的数据或文本文件。外部文件通常用于存储 SAS需要处理的原始数据、SAS程序语句和过程运行结果(HTML, PDF格式)等,SAS有时也会将一些结果写入外部文件。同时,SAS还 可以通过特定的SAS/ACCESS接口软件从其他厂商的软件系统(例如数 据库管理系统(DBMS))文件中读取和写入数据。通过SAS/ACCESS 接口软件建立到DBMS的SAS逻辑库后,SAS软件可以像访问SAS数据 集一样访问DBMS中的表。 1.5 一个简单的SAS程序 在了解了SAS窗口环境并简单了解了SAS逻辑库、数据集这些基础 概念后,就可以在SAS窗口环境编辑一个简单的SAS程序,提交运行并 查看结果了。SAS程序由DATA步和过程步组成。DATA步由关键字 DATA开始,过程步由关键字PROC开始。当然SAS程序还可以包括宏 语言,这个会在本篇后面的章节中专门介绍。 启动SAS窗口环境,在“程序编辑器”窗口输入下面的代码。 libname saslib base 'c:\sas\data'; data saslib.Inventory; input Product_ID $ Instock Price; datalines; P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 ; proc print data= saslib.inventory; run; 代码前面的部分之前已经介绍过。LIBNAME语句定义物理路径在 c:\sas\data的SAS逻辑库saslib。DATA步创建存储在逻辑库saslib下的 SAS数据集Invertory,其中包含3个变量(列),分别为Product_ID、 Instock和Price。在DATA步中SAS会读取紧接着DATALINES语句并以 分号结束的4行输入数据,每行数据按顺序赋值给前面3个变量。PRINT 过程会在结果查看器中打印DATA步创建的数据集Inventory中的数值。 在输入代码后,保证该“程序编辑器”窗口为活动窗口,然后选择菜 单“文件” “保存”,单击“工具栏”的工具项 或按快捷键Ctrl+S保存所 输入的SAS程序语句为“测试程序”。当前的窗口环境如图1.7所示。这时 要保证c:\sas\data文件夹存在,如果没有,在对应的目录创建一个,否 则在“日志”窗口会提示错误及错误信息。选择菜单“运行” 单击“工具栏”中的工具项 “提交”,或 、按快捷键F3提交代码。 默认情况下代码提交执行后,包含运行结果的文件HTML会产生, 并自动显示为当前活动窗口,而主窗口左侧则会显示结果列表,如图 1.8所示。 在图1.8中,数据集Inventory的观测值和变量以表的形式展现在了 HTML格式的“结果查看器”中。其中,“SAS系统”字样是SAS系统默认 标题。可以在PRINT过程之前使用TITLE语句指定输出标题或在PRINT 过程中指定标题,通常我们会指定有意义的文字,比如“仓库库存”。指 定的标题在整个SAS会话期间一直有效,如果要使用其他输出标题或恢 复默认标题,可以再次使用TITLE语句指定。此外,还可以指定结果的 多级标题和脚注,这里不赘述。 图1.7 编辑SAS程序代码 图1.8 SAS程序运行结果 可以通过“结果”窗口的条目在“结果查看器”的HTML文件中导航。 在图1.8所示的“结果”窗口展开“Print:SAS系统”,并单击HTML条目, 该条目对应的结果会自动展示在“结果查看器”的当前位置。 在代码运行过程中,运行日志会显示在“日志”窗口。单击窗口条 的“日志-(无标题)”可显示日志信息,如图1.9所示。日志中给出了语 句执行结果、DATA步中生成数据集的观测数(行)和变量数(列), 以及PROC步读取的观测数,还有运行所有DATA步和PROC步所花费的 实际时间和CPU时间。“日志”窗口还可显示程序中的PUT语句输出,这 个在本示例中没有涉及。 图1.9 注意 SAS代码运行日志信息 要养成提交代码运行后首先查看“日志”窗口信息的良好习 惯,检查日志中是否有错误或警告信息。很多时候,特别是当提交代码 量较大时,即使前面的DATA步或PROC步运行失败,后续代码语句仍 然会继续运行,但是可能会导致不正确的结果。所以必须检查日志中是 否有需要注意的错误或警告信息。 接下来看看“输出”窗口。“输出”窗口展示SAS会话期间SAS语句的 列表输出。SAS窗口环境默认不产生列表输出。尝试在上述代码的基础 上稍作修改:改变输出标题和产生列表输出,并在PRINT过程后关闭列 表输出。修改后的代码如下: libname saslib base 'c:\sas\data'; data saslib.Inventory; input Product_ID $ Instock Price; datalines; P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 ; ods listing; title '仓库库存'; proc print data= saslib.inventory; run; ods listing close; 提交运行后,在默认打开的“结果查看器”中,HTML文件在包含本 次代码输出的同时,还包含了上次提交代码产生的输出。在“结 果”和“结果查看器”窗口都可以看到第二次运行结果的标题为我们在程 序代码里指定的“仓库库存”。展开结果窗口的叶子节点,还可以看到第 二次提交的代码产生了两类输出:HTML输出和列表输出。单击最后一 个条目或单击窗口条的“输出-(无标题)”打开所产生的列表输出,可 以看到数据集数据也输出到了输出窗口,如图1.10右下窗口所示。 图1.10 定制标题和列表输出 到这里,已经了解如何在SAS窗口环境中编辑一个简单的SAS程 序、提交SAS程序并检查SAS程序的运行日志、查看结果和输出。下面 来看看基于SAS 9.4发布的另一可以交互方式提交SAS程序语句的Web应 用SAS Studio。 1.6 SAS Studio SAS Studio是基于HTML5客户端/服务器结构的Web应用。通过SAS Studio,用户能够以使用与SAS窗口环境类似的方式提交SAS程序语 句,运行并获取结果,同时它还提供交互方式指导用户完成分析过程。 用户通过SAS Studio编写的代码或图形界面产生的分析过程会提交 到本地或远程的SAS软件上执行,结果返回SAS Studio客户端。这种特 性会使SAS Studio承担SAS的PaaS(Platform as a Service,平台即服务) 中重要角色,SAS Studio也会集成到SAS云基础设施中。 该产品有着丰富的操作界面,这里简单了解一下。SAS Studio 3.1的 窗口如图1.11所示,其中包含以下3个部分: ·窗口顶部,包含在SAS Studio中开发的应用程序名称和应用程序按 钮。应用程序名称如图1.11中的“程序1.sas”、“分布分析1”和“直方图 1”。 ·窗口的左侧是具有多个可折叠条目的导航面板。导航面板提供的 条目包含“搜索”、“文件夹”、“任务”、“代码段”、“逻辑库”和“文件快捷 方式”。 ·右侧窗口(工作区)包括主要的选项卡,可以显示SAS表,文本文 件(例如,SAS程序文件)、任务等,显示哪一个取决于当前执行的操 作。 图1.11 SAS Studio开发SAS程序 通过这些窗口,用户可搜索文件夹、文件、SAS逻辑库、SAS表、 表的列,还可以查看用户可用的文件夹和文件、代码段、SAS逻辑库及 逻辑库中的表、文件快捷方式等,并且可使用“任务”窗口提供的那些预 先定义的任务,以图形界面操作交互方式实现多种分析过程。如图1.12 所示是逻辑库sashelp中的shoes数据集的列Sales生成直方图的例子。选 择“任务” “图形” “直方图”,在中间子窗口选择数据源和分析变量, 最右侧子窗口会显示自动生成的代码。该代码可保存为代码段或直接提 交到SAS服务器执行生成直方图。 图1.12 SAS Studio生成直方图 1.7 本章小结 学习了本章内容后,相信读者对SAS软件、SAS在Windows和UNIX 环境下的各种启动方式,以及对SAS编程IDE环境(即SAS窗口环境和 SAS Studio)已经有了一些初步的了解,这会为后面的学习打下基础, 可方便读者更加深入地学习SAS软件,并熟练使用SAS窗口环境完成数 据处理和分析任务等。 第2章 读取外部数据到SAS数据集 SAS提供丰富的数据处理和分析方法来解决各种商业问题,但在使 用这些方法之前,往往需要首先将各种形式的数据转换成SAS数据集。 如图2.1所示是在SAS中从原始数据到最后生成有价值信息的过程示意 图。可以看出,在这个过程中,首要工作是将需要分析的原始数据转换 成SAS数据集,然后才是运用各种PROC步对数据集里的数据进行处理 和分析,最后将分析结果以适当的形式展现出来。 图2.1 数据分析处理过程 本章在介绍SAS编程过程中经常使用的SAS逻辑库、SAS数据集、 系统选项以及SAS程序结构等基本概念之后,将会重点讲解如何开发 SAS程序读取外部数据源中的原始数据,创建SAS数据集。最后简单介 绍在SAS程序开发中常见的几种错误现象及其处理方法。 2.1 SAS编程基本概念 在SAS系统中,SAS程序是用来获取外部数据、处理和管理数据, 并对其进行分析预测和优化,从而生成信息报告的重要工具。在学习开 发SAS程序之前,首先需要理解两个基本概念:SAS数据集和逻辑库, 包括它们的命名、引用、类别等;然后得了解SAS中会经常使用的系统 选项,这些系统选项让SAS的分析处理功能既灵活又强大。 2.1.1 SAS逻辑库 SAS逻辑库是一个或多个SAS文件的集合,用于组织、查找和管理 SAS文件。SAS逻辑库管理的SAS文件包括SAS数据集、SAS目录、已编 译的SAS程序,以及多维数据库文件等。在Windows和UNIX环境中, SAS逻辑库通常是包含在同一个文件夹或目录下的一组SAS文件,其他 文件也可以存储在该文件夹或目录下,但只有具有SAS文件扩展名的那 些文件会被认为是该SAS逻辑库的一部分。在其他操作系统下,SAS逻 辑库有不同的实现方式,但通常都对应于当前操作系统用来访问和存储 文件的组织级别。例如在z/OS(OS/390)操作系统下,SAS逻辑库是只 能存储SAS文件的特殊格式化了的主机数据集。尽管实现方式不一样, 但在SAS支持的各种操作系统下,SAS逻辑库的使用方式是相同的。 还有一种SAS逻辑库,叫作元数据边界逻辑库(metadata-bound library),是绑定在由元数据提供安全访问控制的对应表对象上的物理 逻辑库。元数据边界逻辑库里的每个物理表都有指向特定元数据对象的 信息,同时还创建了物理表和元数据对象之间的安全性绑定。该绑定保 证了用户在访问该物理表时,SAS强制执行元数据层权限要求,从而避 免从操作系统直接访问该物理表导致的安全问题。这种SAS逻辑库在 SAS智能分析平台中可用,在本章不作介绍。 1.逻辑库关联 可以通过LIBNAME语句、LIBNAME函数、使用“新建逻辑库”窗口 或操作环境命令来定义逻辑库,并将SAS逻辑库与对应的逻辑库引用名 关联起来。之后便可以通过该逻辑库引用名来读取、写入并更新SAS逻 辑库中的SAS文件。使用LIBNAME语句定义SAS逻辑库的简化语法如 下: LIBNAME 逻辑库引用名 <逻辑库引擎> '逻辑库物理位置'; (1)逻辑库引用名 在定义SAS逻辑库时需要指定逻辑库引用名,临时逻辑库WORK除 外。SAS逻辑库引用名的命名规范如下: ·最大长度是8个字符。 ·必须以字母(从A~Z,大小写均可)或下划线(_)开始。 ·可以是数字(0~9)、字母和下划线(_)的任意组合。 例如,下面的SAS语句定义了SAS逻辑库,其引用名为saslib。 libname saslib base 'c:\sas\data'; 注意 SAS逻辑库与SAS逻辑库引用名是两个比较容易混淆的概 念。SAS逻辑库是SAS文件的集合,SAS文件是其组成部分;而SAS逻 辑库引用名是我们定义逻辑库时赋予这个逻辑库的引用名,并且在以后 可以通过该逻辑库引用名来访问逻辑库中的SAS文件。永久逻辑库(即 其中的SAS文件)会一直存在,而通常SAS逻辑库引用名仅在当前SAS 会话中有效,除非使用“新建逻辑库”窗口指定逻辑库时勾选了“启动时 启用”选项。永久逻辑库和“新建逻辑库”窗口,在后面都有介绍。 (2)逻辑库引擎 SAS逻辑库引擎是SAS软件和SAS逻辑库之间的接口软件组件,每 个SAS逻辑库都关联一种逻辑库引擎。逻辑库引擎识别逻辑库中的文件 并以SAS可理解的格式将文件内容呈现给SAS。SAS提供多种引擎以管 理多个格式的数据。通过这些引擎SAS可以进行如下操作:存储和访问 磁盘文件,将数据从物理位置放入内存中,读取其他软件产生的数据库 文件,以及在不同操作系统之间迁移SAS文件等。 逻辑库引擎可分为原生逻辑库引擎和接口逻辑库引擎。原生逻辑库 引擎也就是默认的Base引擎,访问由SAS创建和处理的SAS文件。在创 建新逻辑库时如果不指定引擎,SAS会自动选择Base SAS引擎。该引擎 根据自身版本的不同,可处理SAS 7、SAS 8和SAS 9文件,这些文件对 应的版本引擎名称分别为V7、V8和V9。大多数情况下,SAS 9可以直 接处理SAS 8、SAS 7和SAS 6创建的SAS文件,不需要对其进行转换。 关于版本兼容性可能存在的问题及对应解决方法,可参考SAS帮助文档 学习。 接口逻辑库引擎用来访问由其他软件系统(例如关系型数据库系 统、ERP系统等)管理的数据。接口逻辑库对用户不透明,需要显式指 定引擎名称,并且需要相应的SAS/ACCESS软件许可,而且通常还需要 安装相应的数据库管理系统的客户端软件。例如要访问Teradata数据库 管理系统中的数据文件,要有SAS/ACCESS to Teradata Interface的许 可,并且在定义逻辑库时,LIBNAME语句中要指定对应的逻辑库引擎 为Teradata数据库对应的引擎,同时还要安装了Teradata提供的客户端软 件。 下面给出了定义原生逻辑库和接口逻辑库示例。 ·Base引擎相关代码如下(语句中base选项可省略): libname saslib base 'c:\sas\data'; ·SAS/ACCESS to Teradata引擎相关代码如下: libname tdlib teradata server=tera2650 user=user1 password=password1 database=hps; (3)逻辑库物理位置 SAS逻辑库物理位置是一个或多个操作系统能够识别的物理位置, 或者是一个或多个已经定义了的其他SAS逻辑库。在上面给出的Base引 擎示例中,逻辑库saslib的物理路径为c:\sas\data。对于连接到数据库管 理系统的SAS逻辑库,通常是通过一系列数据库连接选项指定要访问的 数据库管理系统的信息。如在SAS/ACCESS to Teradata引擎示例中,通 过数据库连接选项分别指定了Teradata数据库服务器的名称、使用的用 户名、密码及数据库名称。 SAS逻辑库还可以有多个物理位置。下面的代码示例给出了定义多 个物理位置的SAS逻辑库Y2014。这3段代码都能实现将逻辑库引用名 Y2014与4个物理位置c:\sas\data\quater1、c:\sas\data\quater2、c: \sas\data\quater3和c:\sas\data\quater4相关联。 ·代码1: libname Y2014 ('c:\sas\data\quater1' 'c:\sas\data\quater2' 'c:\sas\data\quater3' 'c:\sas\data\quater4'); ·代码2: libname libname libname libname libname Q1_2004 'c:\sas\data\quater1'; Q2_2004 'c:\sas\data\quater2'; Q3_2004 'c:\sas\data\quater3'; Q4_2004 'c:\sas\data\quater4'; Y2014 (Q1_2014 Q2_2014 Q3_2014 Q4_2014); ·代码3: libname libname libname libname Q2_2004 'c:\sas\data\quater2'; Q3_2004 'c:\sas\data\quater3'; Q4_2004 'c:\sas\data\quater4'; Y2014 ('c:\sas\data\quater1' Q2_2014 Q3_2014 Q4_2014); 当一个物理位置下的空间不够时,定义多个物理位置的SAS逻辑库 非常有用。这样,在写程序时只需要使用一个SAS逻辑库引用名,当前 面的物理路径空间用尽时,SAS会自动将写入的SAS文件存储到其他物 理路径。 2.永久和临时SAS逻辑库 SAS逻辑库通常为永久数据库。永久SAS逻辑库存储在计算机的固 定存储介质上,当SAS会话终止时不会被删除,其中的SAS文件可以在 后续的SAS会话中继续使用。当使用永久SAS逻辑库中的文件时,通常 需要指定逻辑库引用名作为两层SAS文件名的第一部分,并且要告诉 SAS该文件的存储位置,例如saslib.Inventory,表明读取或写入SAS逻辑 库saslib中的Inventory文件、数据集或目录(Catalog)等,至于Inventory 具体指的是哪种SAS文件,与所使用的上下文环境有关。 同时SAS还提供了一种在SAS会话或作业运行过程中存储临时数据 和文件的临时逻辑库,其引用名为WORK。逻辑库WORK不需要显式指 定,且仅在当前SAS会话或作业执行过程中存在。逻辑库WORK中的文 件在该SAS会话期间可用于任何DATA步或SAS过程,但如果SAS会话 正常结束,WORK库中的文件在SAS会话结束时会被自动删除。一般情 况下,可以通过指定一级名称来读写这个逻辑库中的SAS文件,同样也 可以使用二级名称。例如,要引用临时逻辑库中的SAS文件Inventory, 直接使用Inventory和使用work.Inventory的效果一样。 在开发SAS程序时,如果一次分析包含多个PROC步,通常会将前 一个PROC步产生的中间数据或文件放入临时逻辑库中,供后面的分析 过程使用。在分析完成时,这些中间数据或文件会自动清除。当然,良 好的开发风格应该是在完成任务后,通过代码显式地删除所产生的临时 数据。 3.SAS系统逻辑库 SAS提供了4个特殊的系统逻辑库:WORK、user、sashelp和 sasuser。WORK是临时逻辑库,前面已经介绍过,其他3个都是永久逻 辑库。 user逻辑库可以使用LIBNAME语句、LIBNAME函数、系统选项 USER=或操作系统显式指定。指定user逻辑库后,可以使用一级名称读 取该逻辑库中的文件,就像引用临时逻辑库中的文件一样。一旦定义了 SAS逻辑库user,在SAS程序中使用一级名称读取或写入任何SAS文件 时,SAS都会在该user逻辑库对应的物理位置查找或写入相应的SAS文 件。这时,如果要引用WORK逻辑库中的文件,必须指定带有WORK逻 辑库引用名的二级名称。当SAS会话结束时,存储在逻辑库user里的文 件不会被删除。 sashelp逻辑库包含一组用于控制SAS会话各方面信息的目录 (Catalog)和其他文件。该逻辑库中存储的这些目录和文件适用于任何 使用该SAS系统的用户。用户的个性化设置会存储在sasuser逻辑库中。 如果除了安装Base SAS软件外,还装了SAS的其他产品,那么这些产品 需要用到的一些目录也会包含在sashelp逻辑库中。 sasuser逻辑库包含能够定制SAS特征以满足特定要求的SAS目录。 如果默认的sashelp逻辑库对一些应用程序不适用,那么可以修改它们并 将这些个性化的设置保存在sasuser逻辑库中。例如,在SAS里,可以在 名称为sasuser.profile的个人Profile目录中存储个人默认的功能键设置或 窗口属性。 2.1.2 SAS数据集 SAS数据集是存储在SAS逻辑库中、由SAS创建和处理的SAS文 件,是SAS存储数据的主要方式。SAS数据集包含以表的观测(行)和 变量(列)为形式存在的数据值,以及用以描述变量类型、长度和创建 该数据集时所使用的引擎等信息的描述信息。根据其是否包含真正的数 据值,SAS数据集可分为SAS数据文件和SAS视图。SAS数据文件包含 数据和描述信息,在逻辑库中的成员类型是DATA;而SAS视图不包含 数据值,是指向其他数据源的虚数据集,成员类型是VIEW。下面分别 介绍SAS数据集的文件内容、命名,各种SAS数据文件和SAS视图,以 及它们的创建方式。 1.数据集文件 如图2.2所示给出了SAS数据集的逻辑组件,这些组件可能分布在操 作系统下的不同文件中。 图2.2 SAS数据集逻辑组件 下面来具体看看图2.2中的各个组件。 ·描述信息:描述了SAS数据集自身及其变量的属性,包括观测数、 观测长度、该数据集上次的修改日期等其他信息。其变量的描述信息包 括名称、类型、长度、输入输出格式、标签,以及是否已经为该变量建 立索引等属性。 ·数据值:以矩形表的形式排列。一个数据集可包含若干个观测 (也称为行),每个观测通常由一个或多个变量(也称为列)值组成。 ·索引:是单独的SAS文件,可以为SAS数据文件创建索引,以提供 对指定观测的直接访问。索引文件与其数据文件有着相同的名称,但成 员类型为INDEX。索引可提供对指定观测更快的访问,尤其是对较大的 数据集而言。 ·扩展属性:是定义在数据集或变量之上的元数据。扩展属性使用 DATASETS过程创建,表示为<名称-值>对。 下面以一个简单的SAS数据集为例,来理解数据集的描述信息、观 测和变量。 在SAS窗口提交如下代码生成数据集: libname saslib 'c:\sas\data'; data saslib.Inventory; input Product_ID $ Instock Price; datalines; P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 ; run; 使用CONTENTS过程打印数据集的属性信息,代码如下: proc contents data=saslib.inventory; run; CONTENTS过程生成的结果如图2.3所示。其中包含了上面提到的 数据集信息、主机相关信息和变量信息等。 图2.3 数据集描述信息输出 提交PRINT过程代码,打印数据集信息。 proc print data=saslib.inventory noobs; run; 上面代码打印的数据集如图2.4所示。 图2.4 数据集数据值 该数据集包含4个观测,每个观测表示一种产品的各类信息。这里 有3个变量:Product_ID、Instock和Price,分别表示产品编号、库存数和 价格。其中P001R、12、125等均为数据值。 2.数据集命名 每个SAS数据集的完整名称如下:libref.SAS-data-set.membertype。 共3个组成部分,从左到右依次为逻辑库引用名、数据集名称和成员类 型。在引用数据集时,通常会指定前两个,SAS会根据上下文环境,例 如该数据集出现的位置或数据集的自描述信息,来确定第三个。逻辑库 引用名是与SAS数据集所在物理位置相关联的SAS逻辑库名。当创建新 数据集时,逻辑库引用名表明要将该数据集保存在哪里(位置)。当引 用SAS数据集时,逻辑库引用名会告诉SAS在哪个逻辑库中找到该数据 集。 数据集名称遵守的SAS命名规则如下: ·最大长度为32字符。 ·必须以字母(从A~Z,大小写均可)或下划线(_)开始。 ·可以是数字、字母和下划线(_)的任意组合。 成员类型由SAS指定,例如SAS数据文件的成员类型是DATA, SAS视图的成员类型是VIEW。这些对开发SAS程序是透明的。 在程序语句中创建和使用SAS数据集时,根据数据集所在的逻辑库 或要存储的逻辑库来确定使用一级或二级名称。一级名称只包含数据集 名称,用于读写临时逻辑库WORK中的数据集,或当逻辑库user被指定 时读写逻辑库user中的数据集。二级名称由逻辑库引用名和数据集名称 组成,形式为libref.SAS-data-set,访问除逻辑库user之外的其他永久逻 辑库中的数据集时,均需要使用二级名称。 注意 前面介绍过SAS数据文件和视图都是SAS数据集。SAS不 允许在相同的逻辑库中存在数据集名称相同的SAS数据文件和视图。因 为从语法上来讲,同一程序语句可以同时接受SAS数据文件和SAS视 图,SAS不能从程序语句判断需要处理的是哪一个文件。 虽然在访问WORK或user逻辑库中的数据集时可以用一级名称,即 可省略逻辑库引用名,但为了保持良好SAS代码编写风格,不建议省 略。 3.变量属性 SAS数据集变量的属性包括变量名、类型、长度、输出格式 (format)、输入格式(informat)和标签(label)。输出格式、输入格 式和标签是变量的可选属性。 每个变量的变量名必须遵守的SAS命名规范如下: ·最大长度为32字节。 ·必须以字母(从A~Z,大小写均可)或下划线(_)开始。 ·可以是数字、字母和下划线(_)的任意组合。 变量的类型是字符型或数字型。字符型变量可包含任何值,而数字 型变量只能包含数字值(数字0~9、=、-、点(.)和科学计数法的 E)。变量类型确定了变量的缺失值如何显示。字符型变量缺失值是空 格,而数字型的变量缺失值是点(.)。 SAS以数字值存储日期和时间。默认情况下,SAS的日期值指从 1960年1月1日开始的天数,SAS使用从凌晨开始的秒数存储时间值, SAS的日期时间值(datetime)指从1960年1月1日开始的秒数。该开始 日期也可以通过系统变量YEARCUTOFF指定为其他值。 下面提交如下代码来生成数据集sales,并使用CONTETNS过程和 PRINT过程分别打印该数据集的描述信息和数据值。 libname saslib 'c:\sas\data'; data saslib.sales; infile datalines dsd missover; input Emp_ID $ Dept $ Sales Date; format Sales COMMA10. Date yymmdd10.; informat Date date9.; label Emp_ID="员工ID" Dept="部门" Sales="销售数据"; label Date="销售时间"; datalines; ET001,TSG,$10000,01JAN2012 ED002,,$12000,01FEB2012 ET004,TSG,$5000,02MAR2012 EC002,CSG,$23000,01APR2012 ED004,QSG,,01AUG2012 ; run; proc contents data=saslib.sales; run; proc print data=saslib.sales noobs label; run; 如图2.5所示为CONTENTS过程打印的部分结果,表示该数据集的 变量属性,其中,Date和Sales为数值型变量,Dept和Emp_ID为字符型 变量。如图2.6所示为PRINT过程的打印结果,可以看出,Dept(部门) 的缺失值为空格,Sales(销售数据)的缺失值为点(.)。 图2.5 图2.6 变量属性 打印数据集 变量的长度与类型有关。字符变量的长度可以在定义时给出,否则 其长度为第一次赋值时值的长度,最大长度可到32K。数字型变量的默 认长度是8个字节,也可以指定不同的长度。 除了名称、类型和长度外,还可以定义变量的输出格式、输入格式 和标签,这些都是可选属性。 格式(format)会影响数据值输出的方式。SAS提供了各种字符、 数字和日期时间格式。例如,为了将23000显示为23000,必须使用 COMMAw.d形式的输出格式。其中w表示最大宽度,d为小数位数。比 如,在图2.5中,Date变量的输出格式为“YYMMDD10.”,打印时该变量 的形式则为YYYY-MM-DD(例如2012-01-01)。Sales的输出格式 为“COMMA10.”,对应的数据输出形式则为10000。还可以创建并存储 自定义的格式,具体在第5章介绍。 输入格式(informat)指定数据值以特定的格式读入,从而成为标 准的SAS值。在读取包含字母或其他特殊字符的数字值时必须使用输入 格式。例如,需要把输入值“$23000”读取为数字型的变量,则必须使用 输入格式DOLLARw.d才能正确读入。自定义的格式也可以用作为输入 格式。 SAS提供了丰富的输入输出格式用于从外部文件读取各种日期格式 和显示各种日期格式,以满足对各种日期格式的需要。在上面的示例中 使用输入格式“DATE9.”读入了“01JAN2012”形式的日期,输出时使用的 是输出格式“YYMMDD10.”,从而将存储的数字显示为“2012-01-01”。 变量都可以有标签(label)。标签通常是描述该变量的文本,最大 长度为256个字符。默认情况下,报表以变量名来标识变量,但是可以 将一个标签分配给相应的变量来显示该变量的描述信息。上例中 Emp_ID的标签为“员工编号”,Dept的标签为“部门”,Date的标签为“销 售时间”,Sales的标签为“销售数据”。在代码中可以看到PRINT过程使 用了LABEL选项,这样一来,打印的数据集表头将会使用各变量的标 签而不是变量名称。 4.SAS数据文件 与SAS视图相比较,SAS数据文件是一种在其文件中包含数据的数 据集。SAS数据文件可以由DATA步创建,其名称在DATA语句中指 定,还可以由PROC步创建,其名称通常是在该PROC步语句或PROC步 的OUTPUT语句中指定的。有时,如果程序没有给输出数据集指定名 称,SAS会使用默认名称。 有两种类型的数据文件:原生SAS数据文件和接口SAS数据文件。 原生数据文件是SAS格式的文件,用来存储SAS格式的数据值和描述信 息。接口数据文件是指数据以其他格式存在,并且SAS可以通过 SAS/ACCESS接口引擎访问的数据文件,例如存在于Oracle、DB2、 Sybase、ERP系统中的数据文件。SAS通过SAS/ACCESS接口引擎来访 问这些文件中的数据,并将这些文件当作SAS数据集处理。 SAS程序语句创建的是原生SAS数据文件还是接口SAS数据文件, 取决于该数据文件所属的逻辑数据库。如果该逻辑库是通过Base引擎定 义的,则所生成的数据文件是原生数据文件,如果是通过SAS/ACCESS 接口逻辑库定义的,则所创建的是接口SAS数据文件。 使用DATA步创建SAS数据文件的语法如下: DATA 数据集名称; … SAS语句…; RUN; 其中,SAS语句用于指定数据源。不同的数据源,SAS语句也不尽 相同。例如DATELINES语句表示从程序语句中读取数据,SET语句读 取指定的输入数据集,INFILE语句读取指定的外部数据文件等。 上例中的DATA步给出了使用DATALINES语句读取列举输入数据 的一个示例。该代码首先定义了一个物理路径为c:\sas\data文件夹的 SAS逻辑库saslib,接着以DATA关键字开始的DATA步创建了存储在逻 辑库saslib中的数据集Inventory。 有些PROC步会在PROC语句或PROC步的OUTPUT语句中指定要输 出的数据集。下面的代码使用SUMMARY过程对数据集trucks进行汇 总,该过程的OUTPUT指定将输出写入数据集saslib.sumout中。 libname saslib 'c:\sas\data'; proc summary data=saslib.trucks; var deaths; output out=saslib.sumout n=n; run; (1)SAS数据文件观测数 观测数是SAS数据文件的一个重要属性。SAS数据文件的观测数是 文件中当前观测(行)和已删除观测的总和。可以通过执行 CONTENTS过程或DATASET过程的CONTENTS语句列出数据集的观测 数,所列出的观测数是观测和已删除观测的总和。参考图2.3,该数据 集观测数在CONTENTS过程打印的第一个表格的第一列(“观测”项)中 给出。 了解观测数有助于管理文件大小并评估磁盘空间要求。SAS数据文 件可计算的最大观测数由操作系统的长整型大小决定。 SAS使用所在操作系统内部的长整型数来记录数据集的观测数。在 32位操作系统中,长整型为32位,数据文件的最大观测数是231-1。在64 位操作系统中,当长整型长度为64位时,最大观测数是263-1。在64位的 操作系统中,长整型长度为64位时,SAS数据文件不可能达到最大观测 数,但是在32位操作系统中,达到最大值时有发生,一定要注意这点。 同样需要注意的是,即使在微软的Windows 64位版本的操作系统中,其 长整型数据类型实际使用的是32位模型,以便维持与32位应用的兼容 性。从SAS 9.3开始,也可以通过设置选项 EXTENDOBSCOUNTER=YES来扩展新输出SAS数据文件中的最大观测 数。这样,即使在使用32位长整型存储观测数的操作系统中,所创建的 SAS数据文件的最大观测数也能达到263-1。 当达到最大观测数时,SAS如何处理取决于该数据文件是否有索 引,或是否使用了索引的完整性限制。 ·如果该SAS数据文件有索引或使用了索引的完整性限制(唯一键、 主键和外键),SAS会产生错误消息。可以删除索引或完整性限制并继 续处理。但是因为文件超过了最大观测数,有些功能会受限制(受限制 的功能会在下面介绍)。而且,如果要维持索引或完整性限制,必须重 建该SAS数据文件。从SAS 9.4开始,当创建SAS数据文件时,默认会创 建扩展的观测数。有兴趣的读者可查看SAS帮助文档获取更多信息。 ·如果没有使用索引,SAS会继续后续处理并且接受额外的观测,不 会有消息表明该SAS数据文件已经达到或超过最大观测数。 当文件超过最大观测数时,任何需要观测数的操作都不可用。例 如,返回观测数的SAS过程(例如PRINT过程或CONTENT过程)会返 回缺失值(.);依赖于观测数的SAS过程(例如SORT过程或 COMPARE过程)会返回不可预知的结果;当请求压缩文件时,压缩比 例不可计算;不能创建索引或完整性限制等。为了重获这些功能,可以 重新创建带扩展观测数的SAS数据文件。 (2)审计追踪SAS文件 可以通过DATASETS过程对SAS数据文件创建审计追踪SAS文件, 用来记录SAS数据文件的修改历史。每当观测被删除或更新时,谁在什 么时候修改了什么的信息都会被写入审计文件。许多业务和组织为了安 全都会要求审计追踪。审计追踪维护了历史信息,历史信息可用于追踪 单块数据从录入数据文件开始到离开的所有信息。 在使用APPEND过程对数据文件进行附加操作时,如果因为完整性 限制拒绝导致附加操作失败,审计追踪将是SAS中存储这些失败观测的 唯一技术。可以使用DATA步从审计跟踪文件中抽取失败或拒绝的观 测,然后根据失败原因描述信息来纠正它们,最后将它们重新应用于该 数据文件。 5.SAS视图 SAS视图本身并不存储数据值,它仅包含描述信息和从其他SAS数 据集或从存储为其他软件厂商文件格式的文件中获取数据所需要的信 息。SAS视图的成员类型是VIEW。 可使用SQL过程、ACCESS过程或者DATA语句的VIEW选项来创建 SAS视图。根据创建的方式,视图又分为SQL视图、接口SAS视图和 DATA步视图。其中SQL视图和DATA步视图都称为原生视图, SAS/ACCESS视图称为接口视图。关于SQL视图的内容在本书第6章介 绍。 创建DATA步视图的语法如下: DATA 数据集名称/ view=数据集名称; … SAS语句…; RUN; 与使用DATA步创建SAS数据文件一样,SAS语句根据数据来源的 不同会有所不同。在DATA步视图中可用的数据源可以是原始数据文 件、SAS数据文件、PROC SQL视图、SAS/ACCESS视图或其他数据库 管理系统中的数据文件。下面给出了一段创建DATA视图的示例代码, 数据源为SAS数据文件。 data saslib.invt_vw / view=saslib.invt_vw; set saslib.inventory; run; 上面代码创建了DATA步视图saslib.invt_vw,其数据来自于逻辑库 saslib下的Inventory数据文件。 接口视图通过SAS/ACCESS创建,可读取第三方数据库管理系统 (DBMS)的数据,例如DB2或Oracle。其实,SAS/ACCESS为这些第 三方产品提供了LIBNAME引擎接口,对这些产品,建议使用LIBNAME 和SAS/ACESS对应的引擎来指定SAS逻辑库到DBMS数据,这比使用 ACCESS过程创建接口视图更容易,也更有效。 使用SAS视图有如下优点: ·可以节省磁盘空间,因为SAS视图不存储实际数据,仅仅存储去哪 儿找到数据及数据如何格式化的指令。 ·因为数据总是在执行时才从SAS视图中获取,这样能保证输入的数 据集总是当前的。 ·SAS视图可减少由于数据设计的改变对用户造成的影响。例如,可 以改变存储在SAS视图里的查询信息而不必改变视图结果的特征。 ·使用SAS/CONNECT软件,SAS视图可连接不同主机计算机上的 SAS数据集,然后展示公司分布式数据的集成视图。 SAS视图可用于以下操作:输入其他DATA步或PROC步,将数据 迁移到SAS数据文件或SAS支持的数据库管理系统中,使用PROC SQL 与其他数据组合。 在选择使用SAS数据文件还是SAS视图时需要考虑以下方面: ·数据文件会使用额外的磁盘空间,而SAS视图会占用额外的处理时 间。 ·数据文件变量可在使用前排序和创建索引,而SAS视图在执行过程 中只能以其存在的形式处理数据。 6.特殊的数据集 还有一种特殊的数据集:_NULL_。如果想执行一个DATA步又不 想创建SAS数据集,可以指定关键字_NULL_作为数据集名称。代码如 下: data _null_; SAS会执行该DATA步里面的语句但不会创建新数据集,不会有观 测或变量写入任何数据集。如果一个DATA步的输出不需要存储为数据 集,比如绘制报表,这种处理可更有效地利用计算机资源。 2.1.3 SAS逻辑库和数据集管理 SAS逻辑库和数据集可以通过SAS程序语句进行管理,例如 LIBNAME语句、DATA步、DATASETS过程、APPEND过程和 CONTENTS过程等。DATA步提供了许多功能对SAS数据集进行处理 (本书后面的章节会具体介绍)。 1.LIBNAME语句 LIBNAME的LIST选项可以将一个或多个SAS逻辑库属性打印在日 志中。其基本形式如下: LIBNAME逻辑库引用名 LIST; LIBNAME _ALL_ LIST; LIBNAME的CLEAR选项用于清除一个或多个逻辑库引用名与SAS 逻辑库之间的关联关系。清除了关联关系的逻辑库引用名在SAS会话中 不再有效。其基本形式如下: LIBNAME逻辑库引用名 CLEAR; LIBNAME _ALL_ CLEAR; LIBNAME还有更多对SAS逻辑库的管理功能,可参考SAS帮助文 档进行学习。 2.CONTENTS过程 CONTENTS过程用于显示数据集的描述信息内容,并打印SAS逻辑 库的目录。通常,CONTENTS过程和DATASETS过程的CONTENTS语 句相同。该过程的基本形式为: PROC CONTENTS DATA=数据集名称; RUN; 在前面的各节中已经使用CONTENTS过程显示了数据集属性内 容,如图2.3所示。 3.DATASETS过程 DATASETS过程提供了强大的SAS数据文件管理功能,对数据集而 言,除了可显示数据集的描述信息外,还可用于追加观测、修改变量 名、删除数据集等。对于该过程,可通过SAS帮助文档进行学习。 当需要修改一个数据集的变量属性时,使用DATASETS过程可以在 不读取数据集观测的情况下直接修改变量属性。虽然也可以通过使用 DATA步的SET语句基于一个数据集创建新的数据集,并为新数据集指 定不同的属性来实现相同的功能,但这样SAS会读写所有的观测,造成 资源浪费,当数据集较大时会非常耗时。 4.SAS资源管理器 此外,我们也经常会通过SAS窗口环境的“SAS资源管理器”来管理 逻辑库以及逻辑库中的SAS文件。这里主要介绍SAS提供的对SAS逻辑 库和数据集的一些实用管理操作,具体操作可以根据各个菜单项对应的 向导提示步骤完成,这里不赘述。 (1)逻辑库管理 启动SAS窗口环境,双击“SAS资源管理器”窗口的“SAS逻辑库”图 标,显示该SAS会话可用的所有逻辑库,包括临时逻辑库WORK、系统 逻辑库sashelp、sasuser和用户定义的逻辑库。在“逻辑库”目录下,可以 通过菜单“文件” “新建”,或右键单击空白处从浮动菜单选择“新建”来 创建新逻辑库。“新建逻辑库”对话框如图2.7所示。这里与通过程序语句 定义SAS逻辑库一样,需要指定逻辑库名称、引擎和物理路径信息。还 可以通过勾选“启动时启用”来指定该逻辑库在Base SAS启动时即创建并 启用,或在“选项”输入框里指定相应选项。 右键单击刚才创建的逻辑库saslib,会显示该逻辑库可用选项的浮 动菜单。用户定义的逻辑库浮动菜单如图2.8所示。可以通过该浮动菜 单实现:在该逻辑库中查找成员、为该逻辑库添加新的成员、删除该逻 辑库和显示其属性。 图2.7 “新建逻辑库”对话框 图2.8 逻辑库浮动菜单 (2)数据集管理 通过“SAS资源管理器”可以浏览逻辑库的内容,在浏览时,可以选 定逻辑库的数据集(表)进行操作。可通过浮动菜单完成如下操作:查 看数据集、将数据集导出为Excel文件、复制、删除、重命名SAS数据集 和创建副本等,如图2.9所示。 图2.9 数据集操作 5.VIEWTABLE窗口 在“资源管理器”窗口双击SAS数据集,会在VIEWTABLE窗口中打 开当前SAS数据集。默认打开方式为浏览模式,该模式能保护数据不会 被更改。在浏览模式下,可以定制数据集视图,例如,排序、改变列显 示颜色和字体、显示标签或删除添加变量。选中数据集的一个变量 (列),右键单击显示的浮动菜单如图2.10所示。可选择不同的菜单 项,通过菜单项向导完成这些定制功能。 图2.10 定制查看数据集的视图 在当前活动窗口为VIEWTABLE窗口时,可以通过菜单将浏览模式 修改为编辑模式,选择“编辑” “编辑模式”。在该模式下可以进行在该 窗口修改数据值、按列排序等操作,并且使用菜单“文件” “保 存”或“另存为”,来保存该数据集或建立新的SAS数据集。 2.1.4 SAS系统选项 SAS的复杂之处在于存在众多选项(option),读者需要细心掌 握。根据SAS选项出现的位置、功能和作用范围来区分,一般分为系统 选项、数据集选项和语句选项(即在语句中出现的选项)。本书会不断 地介绍这些选项,建议读者针对这些选项的类别和用法进行总结。 SAS系统选项是影响整个会话过程中SAS程序处理或交互式SAS会 话的指令。SAS系统选项所控制的内容包括SAS输出的外观、SAS对所 使用文件的处理形式、SAS数据集中观测的处理形式(例如OBS选 项),SAS初始化特性(例如MEMSIZE选项),以及SAS如何与主机操 作系统交互等。系统选项被指定时即开始产生影响,直到其被改变。 数据集选项是为数据集操作指定的选项,应用于其所作用的SAS数 据集。有些数据集选项有对应的系统选项或LIBNAME选项,例如选项 OBS=。 在下面的代码中,前一个PROC过程受数据集选项OBS的控制,而 后一个PROC过程受系统选项OBS的控制。 options obs=10; title "数据集选项OBS=生效打印5条观测"; proc print data=sashelp.shoes (obs=5); run; title "系统选项OBS=生效打印10条观测"; proc print data=sashelp.shoes; run; 两个PRINT过程分别打印shoes数据集的前5条和10条观测,结果如 图2.11所示。 图2.11 数据集选项和系统选项 语句选项出现在SAS语句中,用于控制该语句的行为。全局语句中 的选项会有更加广泛的影响,例如LIBNAME=语句选项影响特定逻辑库 的所有执行。如果LIBANME语句的ACCESS选项值为ONLY,则对该逻 辑库中的所有数据集都不能进行更新或写入操作。 1.指定SAS系统选项 SAS系统选项可通过多种方式指定。常见的指定SAS系统选项的方 法有:通过启动SAS时的命令行和配置文件指定,或者SAS启动后通过 OPTIONS语句或SAS系统选项窗口指定。 在UNIX环境下,如果启动SAS时通过命令行将临时逻辑库WORK 的物理路径设置为/SASWORK,则SAS的启动命令行如下: #/opt/SASHome/SASFoundation/9.4/sas -work /SASWORK 如果是通过配置文件指定的,则将该选项写入配置文件中。SAS配 置文件在第1章中已经介绍过了。SAS启动后在OPTIONS语句中指定系 统选项的形式如下: OPTIONS 选项1 <选项2> <选项3> …; 前面已经给出了通过OPTIONS语句给出指定OBS=选项值的示例。 关于通过SAS系统选项窗口指定和查看系统选项值的情况,会在后 面介绍。 2.查看系统选项值 如果数据集选项和语句选项出现在当前起作用的位置,可以很容易 地查看。但是,要看到当前起作用的SAS系统选项值和当前值是通过何 种方式设置的就没那么容易了,因为SAS为系统选项提供了默认值,并 且有多种方式可以设定系统选项值。对此,可以使用OPTIONS过程、 GETOPTIONS函数或通过“SAS系统选项”窗口指定选项名称来查看。 带VALUE选项的OPTIONS过程将指定选项的值、范围及该值如何 设置的信息打印到“日志”窗口的基本形式如下: PROC OPTIONS OPTION=选项名称 VALUE; RUN; 在SAS窗口提交如下代码: options obs=20; proc options option=obs value; run; 在“日志”窗口打印的输出信息如图2.12所示。从该图可以看出, OPTION过程打印选项obs值为20,显示该值是通过OPTIONS语句设置 的。 图2.12 OPTIONS过程打印的选项信息 不使用选项VALUE时仅返回该选项值。 将GETOPTION函数作为%SYSFUNC宏函数的参数,从而获取系统 选项设置的基本形式如下: %PUT %SYSFUNC(GETOPTION(选项名称)); 在SAS窗口提交如下代码: options obs=20; %put %sysfunc(getoption(obs)); 在“日志”窗口打印输出的信息如图2.13所示。从该图可以看出,选 项OBS的值为20。 图2.13 GETOPTION函数的选项信息 在GETOPTIONS函数中还可添加其他参数,显示指定选项的其他信 息,例如默认值、启动时的值,以及该值如何设置等。 3.SAS系统选项窗口 通过“SAS系统选项”窗口菜单可以查看和管理SAS系统选项。选择 菜单“工具” “选项” “系统”,会打开“SAS系统选项”窗口。该窗口对 SAS系统选项进行了分组,如图2.14所示。可以通过“选项组”的浮动菜 单展开该分组或在该分组中查找系统选项。展开选项组、子选项组直到 右侧窗口出现具体的系统选项,可通过具体选项的浮动菜单修改该选项 值或将该选项值重置为默认值。 图2.14 “SAS系统选项”窗口 在左侧窗口单击“日志和过程输出控制” “过程输出”,右侧窗口会 显示“过程输出组的选项”窗格,如图2.15所示。右键单击要修改其值的 系统选项,通过浮动菜单修改该选项值或将该选项值重置为默认值。例 如,右键单击Center,选择“修改值”,在“修改值”对话框选择或设置新 值,单击“确定”按钮保存该选项值。 图2.15 2.1.5 在“SAS系统选项”窗口修改系统选项值 SAS程序结构 SAS程序用于访问、管理、分析和展现数据。其基础组成部分是 DATA步和PROC步,PROC步又称为SAS过程。一个SAS程序可包含以 任意顺序组合的多个DATA步和多个PROC步。 DATA步通常用于创建和操作数据集,还可用于产生定制的报表。 例如,DATA步可用于计算值、检查并修正数据中的错误、将数据存储 到SAS数据集中以便于下次使用,以及通过对存在的数据集取子集、合 并或更新,产生新的数据集。DATA步由关键字DATA开始。 PROC步是一些预先写好的例程,不同的PROC步其功能不同。 PROC步能够用来分析和处理SAS数据集中的数据,并以适当的形式展 现数据和信息。有些PROC步会创建包含该过程结果的新SAS数据集。 PROC步可列出、排序和汇总数据,也可以产生描述性的统计量,并对 其进行分析和优化,从而创建汇总报告、产生图表等。PROC步由关键 字PROC开始。 SAS程序还包含SAS语句,每条SAS语句通常以SAS的关键字开 始,并总是以分号结束。DATA步和PROC步通常包含多条语句。SAS 语句的形式很自由,可以在一行的任何地方开始和结束,每条语句可跨 越多行,多条语句也可以在同一行。语句中的“词”以空格或特殊字符分 开。SAS语句不区分大小写,但是在大多数时候,在引号中的文本是区 分大小写的。 下面通过示例来理解DATA步、PROC步和SAS语句。在图2.16中, SAS语句、DATA步和PROC步都已经标识出来。 图2.16 SAS语句、DATA步、PROC步 可以在SAS程序的任何地方使用注释语句来说明程序的目的、解释 不好理解的程序片段,或者描述复杂程序中的一些步骤或计算原理。注 释有两种基本形式,第一种如下: *消息; 消息为注释的内容,可以是任意长度,但必须写为单独的语句,以 分号结束,且内部不能包含分号。 第二种形式如下: /*消息*/ 消息为注释的内容,也可以是任意长度,可以嵌套任何类型的注 释,还可以包含分号和不匹配的引号。宏语言(在后面章节中介绍)中 使用注释时,必须使用这种方式。 下面是使用注释的几个例子。 例2.1: *Do NOT edit below this line!; 例2.2: /* Do NOT edit below this line! */ 例2.3: /*********************************************************** * PROGRAM SETUP * Use this section to alter macro variables, options, or * other aspects of the test. No Edits to this Program are * allowed past the Program Setup section!! ***********************************************************/ 通过DATA步读取数据 2.2 DATA步是SAS编程中的重要组成部分。本节主要介绍如何使用 DATA步的各种输入方式来读取不同格式的外部文件中的数据,并创建 数据集。同时,还会介绍DATA步的处理过程,相信这会帮助我们更好 地学习和掌握如何使用DATA步读取原始数据。 2.2.1 DATA步处理 DATA步由一组SAS语句组成。首先,由DATA语句创建并命名 SAS数据集;然后SAS会编译并检查其语句的语法,如果语法正确,这 些语句会被执行。在最简单的形式下,DATA步自动输出和返回,这个 过程会一直循环。下面是一个典型的DATA步读取外部文件的处理流 程: 1)编译DATA步中的SAS语句并检查语法。 2)创建输入缓冲区、程序数据向量PDV(Program Data Vector)和 数据集描述信息。 3)从DATA语句开始执行。 4)将PDV中所有变量值(除自动变量_N_和_ERROR_)置为缺失 值。 5)判断是否有数据要读入。有数据要读入,进行下一步;如果没 有,关闭数据集。 6)将数据读入输入缓冲区,并赋值给PDV中的变量。 7)执行其他可执行语句。 8)将观测写入SAS数据集。 9)返回DATA步开始下一个迭代,从第3步开始。 1.编译阶段 当提交DATA步执行时,SAS检查SAS语句的语法并编译它们,将 语句自动翻译为机器代码。SAS进一步处理代码并创建输入缓冲区、 PDV和数据集描述信息。 ·输入缓冲区是内存中的逻辑区域。当程序执行时,SAS会将原始数 据文件中的每条数据记录读入该区域。 ·PDV也是内存中的逻辑区域,SAS在该逻辑区域中构建数据集,每 次一个观测。当程序执行时,SAS从输入缓冲区读取数据值,或使用 SAS语句创建数据值。SAS把数据值赋给变量,然后从PDV中将这些数 据值写入为SAS数据集中的一个观测。 ·PDV中还包含两个自动变量_N_和_ERROR_。_N_变量计算DATA 步迭代的次数,_ERROR_变量作为在执行过程中由数据引起的错误的 信号,0表示没有错误,1表示有一个或多个错误。这些自动变量不会写 入输出数据集中。 ·描述信息是关于每个SAS数据集的信息,前面介绍过。 2.执行阶段 DATA步中的所有可执行语句在每次迭代中都会被执行一遍。如果 输入文件包含原始数据,那么SAS会先读入一个观测到输入缓冲区,然 后读取输入缓冲区中的数据值并将其赋给PDV中的变量。SAS还会计算 由程序语句创建的数据值,并将这些值写入PDV。当程序执行到DATA 步结束时,默认会执行如下操作: 1)把PDV中的当前观测写入数据集。 2)程序循环回到DATA步最开始的地方。 3)PDV中的变量重置为缺失值。注意,在RETAIN语句中指定的变 量和自动变量_N_、_ERROR_不会重置为缺失值。关于RETAIN语句会 在后面的章节介绍。 如果还有另一条记录要读取,那么程序会再执行一遍,SAS会构建 第二个观测并继续,直到没有任何记录。这时,数据集关闭,SAS继续 下一个DATA或PROC步。 下面通过示例来介绍DATA步编译和执行各条语句时输入缓冲区和 PDV中的内容。该示例在之前用过的创建产品库存的DATA步基础上进 行了简单的修改,新增加了变量Cost。代码如下: libname saslib "c:\sas\data"; data saslib.inventory; input Product_ID $ Instock Price; Cost=Price*0.15; datalines; P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 ; run; ·DATA步由DATA语句开始,该DATA语句还创建命名为Inventory 的数据集,并将其存储在saslib逻辑库中。 ·INPUT语句创建了3个变量:Product_ID、Instock和Price。 ·赋值语句创建了额外的变量Cost,表示库存成本为其价格 (Price)的15%。 ·DATALINES语句标识输入数据的开始,数据值后的分号表示输入 数据的结束。 ·RUN语句表示DATA步的结束。 下面借助每一步输入缓冲区和PDV中的数据来理解提交该DATA步 后编译和执行阶段SAS的行为。 1)提交DATA步执行时,SAS首先编译DATA步。编译时,SAS创 建输入缓冲区、PDV和数据集saslib.Inventory的描述信息。PDV包含在 INPUT语句中的变量、赋值语句中的变量Cost,以及自动产生的变量 _N_和_ERROR_。所有的变量,除了_N_和_ERROR_以外,都会初始化 为缺失值。数字变量的缺失值由点(.)表示,字符变量的缺失值由空 格表示。此时的输入缓冲区和PDV中的内容如图2.17所示。 图2.17 输入缓冲区和PDV中的内容(1) 2)语法正确,DATA步开始执行。INPUT语句会让SAS读入第一个 数据,并记录到输入缓冲区。此时的输入缓冲区和PDV中的数据如图 2.18所示。 图2.18 输入缓冲区和PDV中的数据(2) 3)根据INPUT语句中的指令,SAS将输入缓冲区的值赋值给PDV 中的变量。此时的输入缓冲区和PDV中的数据如图2.19所示。 图2.19 输入缓冲区和PDV中的数据(3) 4)SAS执行程序中的下一条赋值语句: Cost=Price*0.15; 该赋值语句计算变量Cost的值,并将该值写入PDV。此时的输入缓 冲区和PDV中的数据如图2.20所示。 图2.20 输入缓冲区和PDV中的数据(4) 5)这时,DATA步的这一次迭代结束,程序自动做如下操作: ·将PDV中的内容作为第一个观测写入数据集,记住,自动变量_N_ 和_ERROR_不会被写入。 ·循环到DATA步最开始处,开始下一个迭代。 ·释放输入缓冲区的记录。 ·在PDV中,将自动变量_N_加1,重置自动变量_ERROR_为0,并 将其他变量设置为缺失值。 以上操作完成后,输入缓冲区和PDV中的数据如图2.21所示。 图2.21 输入缓冲区和PDV中的数据(5) 6)继续执行。INPUT语句查找下一条观测。在这个例子中,存在 下一条观测,INPUT语句将第二条观测读入到输入缓冲区。此时,输入 缓冲区和PDV中的数据如图2.22所示。 图2.22 输入缓冲区和PDV中的数据(6) 7)接下来SAS像构建上一条观测值一样在PDV中构建下一条观测 值,并将PDV的内容写入数据集。SAS执行完赋值语句后,输入缓冲区 和PDV中的数据如图2.23所示。 图2.23 输入缓冲区和PDV中的数据(7) 8)整个过程继续,直到没有其他观测。DATA步迭代次数与读取 的原始数据观测条数一样。 9)SAS关闭数据集Inventory,本DATA步执行结束,退出该DATA 步。 2.2.2 读取外部文本文件中的数据(初级) 在使用DATA步读取外部文本文件中的数据时,需要给SAS提供读 取原始数据所要求的特定信息,例如原始数据的位置、数据集变量和类 型,以及SAS如何读数据等。SAS会根据这些信息创建数据集。 在DATA步中可以使用DATALINES直接输入数据,或通过INFILE 语句指定原始数据文件。本书之前的示例代码所给出的都是通过 DATALINES直接输入数据。但在实际应用中,要分析的原始数据往往 存储在外部文件中。本节主要介绍使用DATA步读取外部文本数据文件 中的数据。 在读取原始数据文件并创建SAS数据集之前,必须先检查原始数据 文件,确定要读取的数据值在原始数据记录中的格式,并基于这些信息 选择读取数据时使用的方法。SAS提供了以下3种基本输入方式: ·列表输入 ·按列输入 ·格式化输入 这3种输入方式可以单独使用,也可组合使用,还可以和SAS提供 的各种修饰符以及指针控制结合使用,例如与输入格式、行控制符、行 指针控制和列指针控制一起使用。 使用DATA步读取外部文件中数据的基本形式如下: DATA 数据集名称; INFILE 数据文件位置; INPUT 变量列表; RUN; 其中: ·DATA语句指定数据集名称。 ·INFILE语句指定原始数据的位置和名称。原始数据文件可以是在 FILENAME语句中定义的文件引用形式或操作系统下的文件路径。 ·INPUT语句用于告诉SAS如何读取数据。 这里简单介绍如何使用FILENAME定义文件引用。在INFILE语句 中可直接使用带路径的外部文件名称,也可以使用事先由FILENAME语 句赋值的文件引用。使用FILENAME语句可以将SAS文件引用与外部文 件或者存储位置(存储有多个外部文件)关联起来,其基本形式如下: FILENAME 文件引用 外部文件|外部文件存储位置; 其中文件引用用于指定文件引用的名称,以字母开始,可包含字 母、数字和下划线,长度不能超过8个字符。外部文件则给出了一个带 完整路径名的外部数据文件的文件名,或者一组文件的存储位置。下面 分别给出在Windows环境下,FILENAME语句指定单个文件和一组文件 的存储位置定义,以及在INPUT语句中如何使用这两种的文件引用。 1)FILENAME语句指定到单个文件的文件引用。 filename invtfile 'c:\sas\data\inventory.dat'; data saslib.Inventory; infile invtfile; input Product_ID $ Instock Price; run; 2)FILENAME语句指定到一组外部文件存储位置的文件引用。 filename extfiles 'c:\sas\data'; data saslib.Inventory; infile extfiles(inventory); input Product_ID $ Instock Price; run; 本章后面的例子都使用后面这种方式引用外部数据文件。 1.列表输入 列表输入(List Input)用于读取原始数据记录中每个字段由至少一 个分隔符隔开,并且数据值中不包含该分隔符的原始数据。列表输入默 认分隔符为空格,连续的分隔符会当成一个分隔符处理,INPUT语句中 包含了简单的变量名称列表。使用列表输入的基本形式如下: DATA 数据集; INFILE 文件引用; INPUT 变量1 <$> <变量2 <$> …>; RUN; 其中: ·数据集指定要生成的数据集。 ·文件引用指定要读入外部原始数据文件。 ·变量1、变量2等是数据集的变量,变量与变量之间用空格分隔。 对于字符变量则需在变量后加字符$。INPUT语句顺序地将原始数据记 录中由空格隔开的数据值读入到所列出的变量中。 (1)原始数据各个字段以空格隔开 当原始数据记录中的数据值以一个或多个空格隔开,且数据值中不 包含空格时,可使用默认的列表输入方式。 文件inventory.dat(位于c:\sas\data目录下)的内容如下: P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 本章所有的示例中,都假定已经为逻辑库引用名saslib指定了SAS逻 辑库,而且为文件引用名extfiles指定了所有外部数据文件所在的位置。 在SAS窗口中提交如下代码: data saslib.Inventory; infile extfiles(inventory); input Product_ID $ Instock Price; run; proc print data=saslib.Inventory; run; INPUT语句会逐行顺序地读取inventory.dat中的数据值,并赋值给 变量。在读取每行数据时,遇到空格就停止读入当前数据值,并从非空 格处读入下一个数据值。 PRINT过程打印的数据集内容如图2.24所示。 图2.24 打印的数据集 上述的列表输入代码存在如下限制: ·使用列表输入时,字符变量长度默认为8个字节。当输入数据长度 超过8个字节时,从输入缓冲区读入PDV的数据值会产生截断。这个问 题可以通过在INPUT语句之前使用LENGTH语句指定该变量的长度或其 他方式来解决。 ·SAS遇到空格时会停止读入当前数据值,这样SAS就不能处理原始 数据记录中的数据值包含空格的情况了。 ·原始数据中必须使用占位符(.)来表示该数据值为缺失值。 (2)使用LENGTH语句指定字符变量长度 指定变量长度的LENGTH语句的基本形式如下: LENGTH 变量1 <$>长度 <变量2 <$>长度 …>; 通常在变量第一次出现时程序会根据上下文环境确定其长度。可以 在INPUT语句前通过LENGTH语句明确指定变量长度。 要读入的数据文件state.dat中的原始数据记录如下,在该记录中州 名全称字符长度超过了8个字节。 WA CA AK AL Washington California Alaska Alabama 如果使用不带LENGTH语句的列表输入的代码如下: data saslib.state; infile extfiles(state); input Short_Name $ State $; run; proc print data=saslib.state noobs; run; PRINT语句打印的数据集内容如图2.25所示,可以看到,第1条和第 2条观测中State变量的数据值不全,都出现了截断。 图2.25 数据值被截断 可在INPUT语句前加LENGTH语句,指定State变量的长度为20个字 符。修改后的SAS代码如下: data saslib.state; length State $20; infile extfiles(state); input Short_Name $ State $; run; proc print data=saslib.state noobs; run; 这时,PRINT打印的数据集的内容如图2.26所示。State变量中的州 名完全写入数据集中了。 图2.26 加LENGTH语句后的输出 可能已经注意到,打印的数据集中变量的顺序发生了变化。这是因 为它们的顺序由DATA步中变量出现的顺序决定,而State变量首先在 LENGTH语句中出现,接着INPUT语句才会出现Short_Name。但是,这 并不影响数据值的读入顺序。在列表输入方式下,读入的数据值顺序由 其在INPUT语句中出现的顺序确定。 (3)使用INFILE语句的选项DLM=指定分隔符 当原始数据中数据记录的数据值未使用空格,而是使用其他分隔符 时,需在INFILE语句中使用DLM=选项,告诉SAS读入数据时需要使用 的分隔符。 下面将上面外部数据文件的内容稍作修改以便比较。文件 inventory_dlm.dat的内容如下,数据记录中的各数据值之间由逗号 (,)隔开。 P001R,12,125.00 P003T,34,40.00 P301M,23,500.00 PC02M,12,100.00 正确读取该数据文件的代码如下: data saslib.Inventory; infile extfiles(inventory_dlm) dlm=','; input Product_ID $ Instock Price; run; proc print data=saslib.Inventory noobs; run; PRINT过程打印的数据集与上例一样,这里不再给出。 使用DLM=选项可处理原始数据记录中数据值中包含空格的情况。 此外,使用DLM=选项的DATA步也可以很好地处理数据中的缺失值。 如果接连有多个指定的分隔符,也会当成一个分隔符处理。但如果分隔 符之间有空格,则该空格会当作缺失值读入变量并写入数据集。例如, 当数据文件inventory_missing.dat的内容如下: P001R,12,125.00 P003T,34,40.00 P301M, ,500.00 PC02M,12,100.00 提交与上例相同的SAS代码,记得将外部数据文件名字改为 inventory_missing.dat。PRINT过程打印的数据集内容如图2.27所示,其 中第3个观测Instock变量为默认值。 图2.27 打印的数据集内容 (4)使用INFILE语句的选项DSD 指定选项DSD后,如果数据值是由引号引起来的,可以将数据值中 的分隔符当成是数据值的一部分读入,字符值中的引号在读入PDV时会 被删除。DSD选项将默认的分隔符设置为逗号,还改变了使用列表输入 时SAS处理分隔符的方式,比如,如果有两个连续的逗号,将被当作缺 失值。 原始数据文件customer_dsd.dat的内容如下,其中客户的地址信息中 包含了逗号,并使用双引号引起来,第一条和第三条记录中的客户名字 缺失。 C001,,"14 Bridge St. San Francisco, CA" C002,Emily Cooker,"42 Rue Marston" C002,,"52 Rue Marston Paris" C005,Jimmy Cruze,"Box 100 Cary, NC" 读取该外部数据文件的SAS代码如下,代码中还使用了LENGTH语 句指定变量长度。 data saslib.customer; length Name $20 Address $40; infile extfiles(customer_dsd) dsd; input Customer_ID $ Name $ Address $; run; proc print data=saslib.customer noobs; run; PRINT过程打印的数据集内容如图2.28所示。同样,因为SAS先编 译LENGTH语句,所以数据集中Name和Address变量出现在Customer_ID 前面了。 图2.28 指定DSD选项后的打印内容 选项DSD还可以和其他选项(例如DLM=和DLMSTR=)一起使 用。DLM=在上面介绍过,DLMSTR=用于指定分隔数据值的字符串。 如果需要,可以查看SAS帮助文档获得更多信息。 2.按列输入 当原始数据记录中的数据值在每条记录中占据相同的列时,可使用 按列输入的方式。按列输入(Column Input)可以读取固定列的数据。 该读入方式的INPUT语句基本形式如下: INPUT 变量1 <$> 开始列<-结束列> <变量2 <$>开始列<-结束列> …>; 其中: ·变量名后有$表示该变量为字符型变量。 ·开始列-结束列指定变量在原始数据记录中所处的位置。 ·变量长度由为该变量指定的列数确定,可以超过8个字节。 ·按列输入可读入包含空格的数据值。 ·可以处理数据中的缺失值,不需要使用占位符。 文件customer.dat的内容如下,其中,第1~14列为产品编号,第 16~26列为附属品牌,第28~29列为专卖店数,第31~35列为产品库存 数。 C001 C002 Emily Cooker C002 C005 Jimmy Cruze 14 Bridge St. 42 Rue Marston 52 Rue Marston Box 100 San Francisco CA Paris Cary NC 读入该原始文件中数据的SAS代码如下: data saslib.customer; infile extfiles(customer); input Customer_ID $ 1-4 Name $ 6-19 Address $ 21-37 City $ 39-51 State $ 53-54 ; run; proc print data=saslib.customer noobs; run; PRINT过程打印的数据集内容如图2.29所示。 图2.29 按列输入后的打印内容 在列表输入方式下,读入的数据值由指定的列号确定,所以使用列 表输入可以以任何顺序读入列,而且可跳过一些列、不读入到数据集中 或重复读取数据记录中的数据值。 3.格式化输入 上面介绍的按列输入与列表输入一样,只能读取标准的字符或数字 值到数据集中。其实,SAS还可以读取特殊格式的数字数据,例如二进 制数据、日期/时间(01FEB2013),或者包含逗号(1,262)、货币符 号($87.3)等特殊字符的数字值。在这种情况下,就需要使用格式化 输入(formatted input)了,即在INPUT语句中提供特殊的指令,以便 SAS正确地读取原始数据记录中的数据值。这些特殊指令称为输入格式 (Informat)。格式化输入组合了按列输入特征和读取非标准化数字或 字符值的能力,保证数据值可正确地从原始数据记录中读入。 对单个变量,格式化输入的INPUT语句的基本形式如下: INPUT 变量 <$> 输入格式; 数值型和字符型最基础的输入格式形式分别为n.和$n,例如10.和 $20.,表示从输入缓冲区的当前列控制指针处分别读取10列和20列,并 赋值给对应变量。对于字符变量,输入格式还指定了变量的长度,而数 字型变量的长度仍然为8。 除了这些基础的格式外,SAS还提供了一些格式用于读入特殊格式 的数字值,例如前面提到的读入带$符号的数字或日期时间值。同时, SAS还支持自定义格式,在本书后面的章节会介绍。 来看个示例,原始数据文件sales.dat的内容如下,该文件中原始数 据记录包含字段依次为员工ID、部门、销售额和上次修改日期,其中销 售额和日期都不是标准数字值,需使用对应的输入格式。 ET001 ED002 ET004 EC002 ED004 TSG $10000 $12000 TSG $5000 CSG $23000 QSG 01JAN2012 01FEB2012 02MAR2012 01APR2012 01AUG2012 读入处理该文件的SAS代码如下,其中Sales和Date变量分别使用了 输入格式comma6.和date9.,Emp_ID和Dept使用的是上面介绍过的按列 输入方式。 data saslib.sales; infile extfiles(sales); input Emp_ID $1-5 Dept $7-9 +1 Sales comma6. @22 Date date9.; run; proc print data=saslib.sales noobs; run; PRINT过程打印的数据集内容如图2.30所示。SAS的日期、时间以 数字形式存储。所以打印的数据集中Date变量的值为数字,其实可通过 输出格式(format)输出为更加易读的形式。 SAS提供了commaw.和datew.等很多格式,用于读取对应的带嵌入 符号和日期的数字值,其中的w表示读入数据的总宽度,包含美元符 号。输入格式将数据值中嵌入的$符号转换成了不带美元符号的数字值 写入到PDV中,并最终写入数据集,如图2.31所示。 图2.30 图2.31 格式化输入数据的打印内容 带格式数据的输入与输出 在INPUT语句中,还使用了相对列控制符号+1和绝对列控制符号 @22,分别表示将当前的输入列控制指针向前移1位和将该指针直接移 动到列22。在上面的示例中,程序读入一行记录到输入缓冲区后,列控 制指针的移动情况如下: ·第1~5列写入Emp_ID,列控制指针在第6列。 ·第7~9列写入Dept,这时列控制指针在第10列。 ·+1将列控制指针移到第11列。 ·开始读入comma6.中指定的6列,即将第11~16列使用输入格式转换 后写入Sales,这时列控制指针在第17列。 ·@22将控制指针直接移到第22列,读入date9.中指定的9列,即第 22~30列,然后使用该输入格式进行转换,并写入Date。 SAS还提供了丰富的输入格式以便读入各种形式的数据值。下面给 出了SAS提供的常用数字、字符、日期、时间、日期时间输入格式,如 表2.1和表2.2所示。关于这些输入格式的详细使用方法,请参考SAS帮 助文档学习。 表2.1 表2.2 常用数字和字符输入格式 常用的SAS日期、时间、日期时间输入格式 4.带修饰的列表输入 在学习格式化输入时,我们知道了输入格式的概念。本节将列表输 入、输入格式和修饰符结合起来,结合后就成了带修饰的列表输入 (modified list input),这样可以使用列表输入方式更灵活地读入数 据。前面讲到列表输入时,提到列表输入的一些限制,例如所创建的变 量长度为8个字节、默认(分隔符为空格时)不能读入包含嵌入空格的 数据值、不能处理带特殊字符的数字值和日期等。SAS提供如下格式的 修改符来消除这些限制,进而增加了列表输入的灵活性。 ·&修饰符(ampersand format modifier):使用列表输入时,该修改 符能够读入数据值中包含一个或多个嵌入空格的字符值,并指定字符的 输入格式。SAS读入数据直到遇到两个连续的空格或达到所定义的数据 长度或输入行结束才停止。&修饰符解决了使用列表输入方式读取数据 值中包含嵌入空格的问题,但要求该包含空格的数据值与下一个数据值 之间至少间隔两个空格。 ·修饰符(colon format modifier):使用列表输入时,该修改符可以 在变量名后指定输入格式。SAS读入数据直到遇到空列、达到所定义的 数据长度(对字符型变量来说)或输入行结束才停止。:修饰符可以读 取超过8个字节的字符数据和包含特殊字符的数字字符。 ·~修饰符(tilde forat modifier):可以读入并保持数据值中的单引 号、双引号和分隔符。 接下来通过示例来学习修饰的列表输入。注意,下面的示例中没有 使用~格式修改符,有兴趣的读者可参考SAS帮助文档进行学习。 原始数据文件customer2.dat的内容如下,每条记录包含联系人信 息:客户ID、名字和出生日期,其中名字里面嵌入了空格,可使用&修 饰符读入。注意,使用&修饰符要求名字和出生日期之间为两个空格。 C001 C002 C002 C005 Willam Smith Emily Cooker Geroge Collin Jimmy Cruze 22Oct1970 01JAN1978 09MAR1968 25Jun1972 处理该数据的SAS代码如下,其中,Name变量使用了&修饰符读入 带空格的名字,并指定输入格式为$20.,所以Name变量的字符长度为20 个字节,而且SAS会将缓冲区中的20个字符读入Name。Birth_Date使用 了:修饰符读入日期格式的数据。 data saslib.customer2; infile extfiles(customer2); input Customer_ID $ Name & $20. Birth_Date:date9.; run; proc print data=saslib.customer2 noobs; run; PRINT过程打印的数据集如图2.32所示,所有数据值都正确读入到 了数据集中。 图2.32 带修饰的列表输入数据的打印内容 5.命名输入 命名输入(named input)读取包含变量名、等于符号和变量值的输 入数据,例如Name=Willam。命名输入的基本形式如下: INPUT 变量1= <$> <变量2= <$> …>; 使用命名输入方式时,INPUT语句从输入指针的当前位置读取输入 数据。如果原始数据记录开始处不是命名形式,则可以在开始部分使用 其他输入风格,接着再使用命名方式读入数据。但是,一旦INPUT语句 开始使用命名输入方式,接下来的数据值则必须也是命名形式。 例如,原始数据文件named.dat内容如下,第二、三个字段为命名形 式。 C001 C002 C002 C005 Name=Willam Age=43 Name=Emily Age=35 Name=Geroge Age=45 Name=Jimmy Age=41 使用命名输入读入第二、三个字段Name和Age,第一个字段 Customer_ID变量使用列表输入。代码如下: data saslib.customer; infile extfiles(named); input Customer_ID $ Name= $ Age=; run; proc print data=saslib.customer noobs; run; PRINT过程打印的SAS数据集内容如图2.33所示。 图2.33 命名输入数据的打印内容 命名输入还可以和其他语句(例如LENGTH语句定义变量长度)以 及其他输入方式结合,提供更多的灵活性来读取外部数据文件。 6.混合输入 在使用INPUT语句时不限于使用一种输入方式,可以在一条INPUT 语句中混合使用这些输入方式,只要可以适当地描述原始数据记录就 行。在前面格式化输入的示例中已经组合了按列输入方式和格式化输入 方式,这里再将3种基础输入方式组合。 原始数据文件mixedinput.dat的内容如下,其中依次包括了课程编 号、课程名称、开课日期和报名人数等信息。 PM01 PM02 TM01 SY05 SAS SAS SAS SAS Base Programming Advanced Programming Text Mining System Administration 22Oct2013 01JAN2013 09MAR2013 25Jun2013 22 15 8 12 列表输入读取Course_ID和Attendee、按列输入读取Course_Name、 格式化输入读取Open_Date。代码如下: data saslib.mixedinput; infile extfiles(mixedinput); input Course_ID $ Course_Name $ 6-30 @32 Open_Date date9. Attendee; run; proc print data=saslib.mixedinput noobs; run; PRINT过程打印的数据集内容如图2.34所示。各个数据值都被正确 地从数据文件读入,并写入数据集。 图2.34 2.2.3 混合输入数据的打印内容 读取外部文本文件中的数据(高级) 在创建SAS数据集时,根据数据文件的格式,经常会需要使用更加 高级的特征来正确有效地将原始数据文件的记录(或行)转换为数据 集。例如: ·对原始数据记录中的数据长度不够INPUT语句中所有变量读取的 情况进行处理; ·在创建SAS数据集的观测前先测试条件是否成立; ·在单条数据记录中创建多个观测; ·从多个原始数据记录中创建单条观测; 1.使用INFILE语句的选项 当DATA步从外部文件中读取原始数据记录时,如果SAS在为所有 变量读到数据之前就遇到了输入行的末尾,可能会有问题,尤其是当 INPUT语句要求读入的变量长度超过输入缓冲区中的数据,或记录中包 含缺失值又没有占位符时。默认情况下,SAS读入一行记录,当INPUT 语句在当前输入行找不到所有的数据值时,会自动读入下一行数据记 录,DATA步中的语句继续执行,但可能产生意外结果(即未按预期读 入数据)。这时,可以在INFILE语句中指定MISSOVER、 TRUNCOVER或STOPOVER选项,从而改变SAS的默认行为。 ·FLOWOVER:即上面描述的默认情况,INPUT语句会读入下一条 记录到输入缓冲区中,给当前PDV中未赋值的变量赋值。 ·MISSOVER:会在DATA步的本次迭代中阻止INPUT语句读入原始 数据的下一条记录,并将PDV中所有未赋值的变量保持为缺失值(PDV 中变量未赋值时就为缺失值)。当原始数据记录中的最后一个或多个字 段没有值,并且希望SAS将对应的变量置为缺失值时使用MISSOVER。 ·TRUNCOVER:也会阻止INPUT语句读入原始数据的下一个记 录,但对于当前正在读入的变量,当输入缓冲区中的数据长度少于当前 变量要求的长度时,则将当前输入缓冲区中的数据赋值给PDV中的该变 量。所有未赋值的变量置为缺失值。 ·STOPOVER:DATA步的执行会停止。 下面通过一些示例来帮助理解这些选项的作用。 例2.4:选项MISSOVER。 外部数据文件missover.dat的内容如下,依次包括课程编号、课程名 称、参加课程人数和讲师姓名等信息。其中第二条记录中未提供参加课 程人数和讲师姓名,也没有占位符。 PM01,SAS PM02,SAS TM01,SAS SY05,SAS Base Programming,22,Greg William Advanced Programming Text Mining,8,Kevin Wynne System Administration,12,Jenny Hagen 下面为使用默认的FLOWOVER选项代码: data saslib.default; length Course_Name $30 Instrutor $20; infile extfiles(missover) dsd; input Course_ID $ Course_Name $ Attendee Instrutor $; run; proc print data=saslib.default noobs; run; PRINT过程打印的数据集如图2.35所示。 图2.35 未使用选项MISSOVER的输出 所生成的数据集中共有3个观测。在第二个观测中,Instrutor变量值 为原始数据文件中第三条记录的部分记录,Attendee为缺失值。这是因 为在第二次迭代时INPUT语句读完Course_Name后,发现第二条原始数 据记录已经没有值可供INPUT语句给PDV中的Attendee和Instrutor赋值 了,所以SAS读入第三条原始数据记录到输入缓冲区中。这时输入缓冲 区的第一个数据值“TM01”为字符值,与Attendee(数字型变量)的类型 不匹配,所以PDV中Attendee为缺失值,第二个数据值“SAS Text Mining”则赋值给了PDV中的Instructor变量。接着,进行DATA步的第三 次迭代,读入第四条原始数据记录。 查看日志窗口中DATA步执行的日志,如图2.36所示。其中提示信 息表示“TM01”对数值型变量无效。而且“NOTE:INPUT语句到达一行 的末尾,SAS已转到新的一行。”这句注释表示SAS在该次迭代中间(正 常情况下,仅在迭代开始时读入数据行)从输入数据文件中读入了新 行。 图2.36 DATA步执行的日志 下面在INFILE语句中加上MISSOVER选项,代码如下: data saslib.missover; length Course_Name $30 Instrutor $20; infile extfiles(missover) dsd missover; input Course_ID $ Course_Name $ Attendee Instrutor $; run; proc print data=saslib.missover noobs; run; PRINT过程打印的数据集如图2.37所示。可以看到,原始数据记录 如我们所预期的那样读入了数据集中。 图2.37 使用MISSOVER选项的输出 例2.5:选项TRUNCOVER。 默认情况下(选项为FLOWOVER),当原始数据记录长度小于 INPUT语句的预期时,INPUT语句自动读入下一条数据记录。当指定选 项TRUNCOVER时,即使当前输入行数据的长度小于INPUT语句的预 期,也会将当前输入行的数据赋值给当前处理的变量,并将其他没有赋 值的变量设置为缺失值。 TRUNCOVER选项常用于处理变长的原始数据记录,可在INPUT语 句中定义足够长度的变量,即使当前数据记录中的数据长度小于变量指 定的长度,也可以将该记录从缓冲区读入PDV,并写入数据集,以便进 一步处理。 原始数据文件comments.dat的内容如下,共3条记录,全部为文本, 文本长度不确定。 Action brought Community Plant Variety Office Action brought Community Plant Variety Office Parties Applicants Commission Regulation September 1995 concerning the second list of priority substances as foreseen under Council Regulation Commission Regulation establishing the standard import values for determining the entry price of certain fruit and vegetables Commission Regulation (EC) No 使用TRUNCOVER选项读入该文件记录。设置变量Text的输入格式 为“$500.”,当原始记录中文本长度不足500个字符时,TRUNCOVER选 项会将当前输入缓冲区中的所有内容写入PDV,并写入数据集。 data saslib.truncover; infile extfiles(comments) truncover; input Text $500.; run; proc print data=saslib.truncover noobs; run; PRINT语句打印的数据集内容如图2.38所示。可以看到,所有的评 论信息都读入了数据集中。 图2.38 使用TRUNCOVER选项的输出 MISSOVER与TRUNCOVER的不同之处在于,如果当前变量没有读 到要求长度的数据,MISSOVER会将当前变量的值也置为缺失值。还是 以上面的示例为例,如果将TRUNCOVER换成MISSOVER,所生成的数 据集中3个观测值都为缺失值。 例2.6:选项STOPOVER。 当INPUT语句在当前输入行找不到所有数据值时,如果在INFILE语 句中使用选项STOPOVER,SAS会停止执行该DATA步。 使用前面用到的数据文件missover.dat,内容如下: PM01,SAS PM02,SAS TM01,SAS SY05,SAS Base Programming,22,Greg William Advanced Programming Text Mining,8,Kevin Wynne System Administration,12,Jenny Hagen 使用STOPOVER选项的代码如下: data saslib.stopover; length Course_Name $30 Instrutor $20; infile extfiles(missover) dsd stopover; input Course_ID $ Course_Name $ Attendee Instrutor $; run; proc print data=saslib.stopover noobs; run; DATA步执行的日志如图2.39所示。此时,在日志窗口会给了出错 误消息“INPUT语句超过记录长度”,并且SAS系统停止处理DATA步。 图2.39 日志中输出错误消息 PRINT语句打印的数据集内容如图2.40所示。可以看到,所生成的 数据集中只有一个观测。因为在处理第二条原始数据记录时,输入数据 缓冲区中没有供Attendee和Instructor变量读取的数据,SAS处理停止执 行该DATA步了。 图2.40 使用STOPOVER选项的输出 2.使用选项LRECL和在INFILE中使用选项PAD 选项LRECL为系统选项指定用于读写外部文件的默认逻辑记录长 度。LRECL指定逻辑记录的长度为1(字节)或1024(k字节)的倍数。 例如32表示32字节、16k表示16384字节。该选项的范围为1~32767。在 SAS 9.4中,LRECL系统选项默认值为32767,通常不需要修改。 PAD和NOPAD选项控制SAS是否使用空格对从外部文件读入的记 录进行填充,使其达到选项LRECL=指定的长度。默认设置为NOPAD。 当使用PAD选项时,SAS会自动用空格填充从外部文件中读入的记 录长度。 还是以上面的comments.dat文件为例。 Action brought Community Plant Variety Office Action brought Community Plant Variety Office Parties Applicants Commission Regulation September 1995 concerning the second list of priority substances as foreseen under Council Regulation Commission Regulation establishing the standard import values for determining the entry price of certain fruit and vegetables Commission Regulation (EC) No 下面在INFILE语句中使用PAD选项,代码如下: data saslib.comments; infile extfiles(comments) pad; input Text $500.; run; proc print data=saslib.comments noobs; run; PRINT过程打印的数据集内容如图2.41所示。DATA步正确读入了 文件中的所有评论。 图2.41 在INFILE语句中使用PAD选项 3.使用INPUT语句的行保持符和行控制符 在一个DATA步中可有多个INPUT语句。默认情况下,当执行到 INPUT语句时,程序会自动将下一条数据记录放入输入缓冲区,并且, 每次迭代完成后SAS会返回DATA步的开始处,同时,输入缓冲区中的 数据会自动清除。可以在INPUT语句中使用行保持符和行指针控制符改 变这种行为模式。 (1)行保持符 如果在INPT语句的结束处指定单个@的行保持符(Line-hold Specifier),输入缓冲区的数据则会保持住,当前迭代中的下一条 INPUT语句可以继续使用。SAS会维护列控制指针在输入缓冲区中的位 置。但是,当程序返回DATA步开始处执行时,输入缓冲区中的数据会 被释放。 在INPUT语句的结束处使用有两个@的行保持符(形式为@@) 时,输入缓冲区中的数据会保持住,直到读到数据记录的结尾内容为 止。这样,程序在执行INPUT语句时就不会自动将下一条数据记录读入 到输入缓冲区中,同时,程序返回DATA步的开始处继续执行时,输入 缓冲区的数据也不会释放。但是SAS会维持指针在当前记录中的位置, 这样,程序就能够读取记录中的每个值了。 (2)行指针控制符 在INPUT语句中还可使用行指针控制符/和#n。 行指针控制符/会强制读入下一条记录到输入缓冲区,并将列指针 放入该行记录的开始处。该行指针控制符常用于跳过原始数据记录中的 数据值。 #n会将当前指针移至多行输入缓冲区的对应行。当INPUT语句中出 现行指针控制符#n时,编译程序会创建一个多行输入缓冲区,输入缓冲 区的行数等于INPUT语句中出现的最大n值。这样,执行时程序可以一 次读入多行数据到输入缓冲区。通过这种方式,INPUT语句可以以任意 顺序读入数据值。 下面通过几个示例来讲解行保持符@和@@,以及行指针控制符/和 #n的使用方式。 (3)创建一个观测前测试条件 在只需读入输入文件中的部分满足条件的数据时,可能需要先读入 部分变量值,判断这些变量值是否满足某种条件。如果满足,继续读入 当前缓冲区中的其他数据值。如果使用了默认INPUT语句,会在第二次 执行INPUT语句时将下一条记录读入输入缓冲区,之前输入缓冲区中的 数据则会被冲掉。这时可以使用行保持符@将该记录保持在输入缓冲区 中,第二次执行INPUT语句时程序不会重新读入新的数据记录,而是直 接使用当前输入缓冲区中的数据。 原始数据文件Sales.dat的内容如下,每条记录中包含员工ID、部 门、销售额和上次修改时间等信息,现在要求仅读入部门TSG的员工并 创建数据集。 ET001 ED002 ET004 EC002 ED004 TSG $10000 $12000 TSG $5000 CSG $23000 QSG 01JAN2012 01FEB2012 02MAR2012 01APR2012 01AUG2012 下面的DATA步创建仅包含部门TSG员工的数据集: ·第一条INPUT语句读入输入数据的第7~9列到Dept变量中,并使用 @行保持符告诉SAS将输入记录保持在缓冲区。 ·IF语句判断变量Dept的值是否为TSG。如果不是,程序返回DATA 步的开始处进行下一个迭代;如果是,执行该次迭代的下一条语句。 ·第二条INPUT语句从输入缓冲区中按列读入和格式化读入其他变 量值。代码如下: data saslib.sales; infile extfiles(sales); input Dept $7-9 @; if Dept='TSG'; input Emp_ID $1-5 +5 Sales comma6. @22 Date date9.; run; proc print data=saslib.sales noobs; run; PRINT过程打印的数据集的内容如图2.42所示。可以看到,数据集 中只包含部门(Dept)为TSG的观测。 图2.42 仅包含部门TSG的员工 (4)读单条记录创建多个观测 当原始数据文件的一条记录中存在要创建的数据集的多个观测时, 应在INPUT语句中使用两个行保持符@@,这样,程序执行到下一个 INPUT语句,甚至下一个迭代时,都不释放输入缓冲区中的记录, INPUT语句会继续从输入缓冲区中读取数据值,直到INPUT读完缓冲区 中所有的数据值为止。 数据文件inventory2.dat的内容如下,每行包含两种商品的信息。 P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 这时使用@@行保持符将记录一直保持在输入缓冲区,直到缓冲区 中的所有数据值都被读取。代码如下: data saslib.Inventory; infile extfiles(inventory2); input Product_ID $ Instock Price @@; run; proc print data=saslib.Inventory noobs; run; PRINT过程打印的数据集如图2.43所示。可以看到,所有4种商品信 息都读入了数据集中。 图2.43 4种商品信息都读入数据集中 (5)从多条记录中创建一个观测 有时一个客户的联系信息,或者一种商品的属性信息会分布在多个 行中,而我们只关心部分数据行的信息,这时可以使用单独的空INPUT 语句或/行指针控制符来跳过不关心的行。 参考如下原始数据文件Customer3.dat的内容。这里打算将同一个联 系人的多行信息读入为数据集的一条观测,但是不包含街道或邮箱信息 (即每组信息的第二行)。 Greg William 14 Bridge St. San Francisco CA Emily Cooker 42 Rue Marston New york NY Jimmy Cruze Box 100 Cary NC 方法1:使用多个INPUT语句。 此方法在一个DATA步中使用多个INPUT语句,每个INPUT语句读 入新行到输入缓冲区,并从输入缓冲区中读入指定变量值到PDV。当 DATA步结束时,将PDV中的变量值写入数据集。代码如下: data saslib.Customer; infile extfiles(Customer3) truncover; input Name $20.; input; input City $20.; input State $2.; run; proc print data=saslib.Customer noobs; run; 在上述代码的执行过程中: ·第一个INPUT语句读入第一行,并将其内容赋值给PDV中变量 Name。 ·第二个INPUT语句读入第二行。 ·第三个INPUT语句读入第三行,并将其内容赋值给PDV中变量 City。 ·第四个INPUT语句读入第四行,并将其内容赋值给PDV中变量 State。 DATA步结束时,PDV中的变量写入数据集。同样,在下一个迭代 中将第五行、第七行、第八行数据赋值给PDV中的变量Name、City和 State,然后写入SAS数据集。PRINT过程打印的数据集如图2.44所示。 图2.44 将多行信息读入为一条观测 方法2:使用/行指针控制符。 也可以使用/行控制符,强制读入新行到输入缓冲区,并赋值给变 量。代码如下: data saslib.Customer; infile extfiles(customer3) truncover; input Name $20. / / City $20. / State $2.; run; proc print data=saslib.Customer noobs; run; 在上述代码的执行过程中: ·INPUT语句读入第一行,并赋值给PDV中的变量Name。 ·//强制依次读入两行,即第二行和第三行,第三行的值会覆盖第二 行,并赋值给PDV中的变量City。 ·/读入第四行,赋值给PDV中的变量State。 DATA步结束时,将PDV中的变量写入数据集。同样,在下一个迭 代中,将第五行、第七行、第八行的数据赋值给PDV中的变量Name、 City和State,然后写入SAS数据集。PRINT过程打印的数据集如图2.45所 示。 图2.45 使用/行指针控制符 方法3:使用#n行指针控制符。 还可以使用#n行指针控制符,直接在多行的输入缓冲区中移动行指 针。代码如下: data saslib.Customer; infile extfiles(customer3) truncover; input #3 City $20. #1 Name $20. #4 State $2.; run; proc print data=saslib.Customer noobs; run; DATA步在编译时会创建一个4行的输入缓冲区,因为n的最大值为 4。执行过程中: ·INPUT语句一次读入4行记录到输入缓冲区。 ·#3将行输入指针移动到输入缓冲区的第三行,将数据值读入到 PDV中的变量City。 ·#1将行输入指针移动到第一行,将数据值读入到PDV中的变量 Name。 ·#4将行输入指针移动到第四行,将数据值读入到PDV中的变量 State。 DATA步结束时,PDV中的变量写入数据集。同样,在下一个迭代 中,INPUT语句会将第五行至第八行数据一次读入到输入缓冲区。再从 输入缓冲区的第三行、第一行和第四行中分别读入数据赋值给PDV中的 变量City、Name和State,然后写入SAS数据集。 PRINT语句打印的数据集内容如图2.46所示。这里的观测数和数据 值相同。 图2.46 使用#n行指针控制符 该数据集与前两个数据集的变量顺序不同,是因为在这3个示例中 的变量在DATA步中出现的顺序不同。 注意 本例为了说明使用#n行控制符可以随意在输入缓冲区的行 之间移动,特意指定读取顺序为第三行、第一行和第四行。若INPUT语 句按如下形式编写,则所生成数据集的变量顺序也与前两种方式一样: input#1Name$20.#3City$20.#4State$2.; 这3种方法都要求单条观测在原始数据文件中占用的行数一样。例 如,在本例中都是4行,而且要读取的同类别信息必须出现在相同的相 对位置上,例如在每4行记录中,第一行是客户姓名、第三行是所在城 市、第四行是所在州。 2.3 通过IMPORT过程读取外部文件数据 除了可以通过DATA步读取外部文本文件数据外,SAS还提供了 IMPORT过程,通过它可以从外部数据源读取数据并写入SAS数据集 中。而且,如果使用SAS/ACCESS to PC Files,IMPORT过程除了可以 导入带分隔符的文件外,还可以读取PC文件中的外部数据,包括 Microsoft Access数据库文件、Miscrosft Excel工作簿、Lotus 1-2-3文件、 dBase文件、JMP文件、SPSS文件、Stata文件、Paradox等。SAS变量的 定义根据输入记录确定。 在SAS窗口环境选择菜单“文件” “导入数据”,可以打开“导入”窗 口,通过“导入向导”可读取上述类型的数据。导入过程所生成的SAS语 句也可以保存起来供以后使用。 对应于IMPORT过程和SAS窗口环境的IMPORT向导,SAS还提供 了EXPORT过程和EXPORT向导(选择菜单“文件” “导出数据”),以 便将SAS数据集中的数据导出到上述类型的文件。对此这里不做讲解, 有兴趣的读者可以通过SAS帮助文档学习。 IMPORT过程的导入数据的基本形式如下: PROC IMPORT DATAFILE=文件名|文件引用 | DATATABLE=表名 DBMS=数据源标识符 OUT=数据集名称; RUN; 其中: ·DATAFILE=指定输入文件的完整路径和文件名,或文件引用。文 件引用通常通过FILENAME语句指定。 ·DATATABLE=指定输入DBMS表名。数据源可以通过DATAFILE 或DATATABLE指定。 ·DBMS=指定要导入的数据类型。SAS支持多种数据类型,例如 CSV、TAB、ACCESS、XLSX、XLS、EXCEL、JMP、DTA、SPASS 等。其中CSV、TAB表示要导入的数据文件分别由逗号和tab符号分隔, ACCESS表示使用LIBNAME语句的Miscrosoft Access表,XLSX表示 Micorsoft Excel 2007或2010的工作簿,等等。可参考SAS帮助文档了解 关于导入数据类型和各类型的详细信息。 ·OUT=指定输出的数据集名称。该语句后面还可以添加数据集选 项。 下面给出了几个例子,分别讲解通过IMPORT过程导入CSV文件、 Microsoft Excel工作簿和Microsoft Access数据库文件中的数据。 1.读取CSV文件 外部文件contact.csv的内容如下,文件第一行给出了各个数据行中 数据值字段的名称,后面的各行则为对应的字段值。 Name,Age,Position,Marriage,Address Greg William,42,Manager,Single,"14 Bridge St. San Francisco, CA" Emily Cooker,33,Sales,Married,"42 Rue Marston" Henry Cooper,,Office,Married,"52 Rue Marston Paris" Jimmy Cruze,34,Manager,Single,"Box 100 Cary, NC" 使用IMPORT过程导入该文件的代码如下: proc import out=saslib.contact datafile="c:\sas\data\contact.csv" dbms=csv replace; getnames=yes; datarow=2; run; proc print data=saslib.contact noobs; run; 所生成的数据集为saslib逻辑库中的contact数据集,数据文件为c: \sas\data\contact.csv,选项DBMS=指定数据库类型为csv。其中文件扩展 名可以省略,SAS会根据DBMS=选项指定的据库类型自动加上。 代码中还使用了REPLACE选项,表示当OUT=指定的数据集存在时 覆盖该数据集。GETNAMES语句表示是否从该文件中的第一行读取变 量值,默认为YES,表示读取;值为NO表示不读取,这时IMPORT过程 会自动产生名为F1、F2、F3等的变量。 DATAROW=语句也会经常使用,用于指定IMPORT语句开始读数 据的行号。默认情况下,当GETNAMES=NO时,DATAROW=1,当 GETNAMES=YES时,DATAROW=2。该选项用于跳过数据文件开始处 的多行内容。 PRINT过程打印的数据集内容如图2.47所示。 图2.47 PRINT过程打印的数据集 2.读取Miscosoft Excel工作簿 IMPORT过程可以导入Microsoft Excel工作簿中的数据。在Excel 2007中文件的工作簿pag的内容如图2.48所示。在该图中,共包含了A~E 共5列,第一行为字段名称,从第二行开始为数据值。 图2.48 工作簿pag的内容 下面使用IMPORT过程读入该工作簿的指定区域。 proc import out=saslib.contact datafile="c:\sas\data\contact.xlsx" dbms=xlsx replace; range='pag$A1:E5'n; run; proc print data=saslib.contact noobs; run; IMPORT过程中可以使用RANGE=语句指定所导入的区域。在使用 IMPORT过程处理工作簿的数据之前,可先通过Microsoft Excel的“名称 管理器”定义要处理的数据的区域,在IMPORT过程中,使用RANGE= 语句指定该数据区域的名称,或直接在RANGE=语句中指定数据区域。 本例中为直接指定,区域为工作簿pag中从A1到E5的矩形区域。PRINT 过程打印的数据集的内容如图2.49所示。 图2.49 注意 从A1到E5的区域 1)DBMS=XLSX可以处理Microsoft Excel 2007或Microsoft Excel 2010的工作簿。对于其他更早版本的Microsoft Excel生成的工作 簿,需使用其他类型,例如XLS、EXCEL4、EXCEL5。 2)还可使用EXCEL数据库类型EXELCS,并通过SAS PC文件服务 器来读取相应版本的Excel工作簿。 3)也可以直接使用LIBNAME语句,通过SAS/ACCESS EXCEL引 擎来访问Excel工作簿。 具体请参考SAS帮助文档。 通过DBMS=XLS或DBMS=XLSX来读取Excel文件中的数据还有一 个好处,即可以直接在UNIX环境下读取Excel工作簿中的数据,不需要 访问PC文件服务器。 3.读取Microsoft Access数据库文件 Microsoft Access是一个桌面关系型数据库系统,通常使用Microsoft ACE引擎(.accdb文件格式)或Microsoft Jet引擎(.mdb文件格式)。在 IMPORT过程中指定DBMS为ACCESS,SAS可以读取在Microsoft Access 97、Microsoft Access 2000、Microsoft Access 2003、Microsoft Access 2007和Microsoft Access 2010中的文件。例如: proc import out=saslib.customer datatable='customer' dbms=access replace; database="c:\sas\data\customer.accdb"; RUN; 在IMPORT过程中使用access数据库类型,实际使用的是 SAS/ACCESS LIBNAME引擎。也可以直接使用LIBNAME语句通过 SAS/ACCESS ACCESS引擎来访问Microsoft Access数据库文件。这里不 做介绍,有兴趣的读者可参考SAS帮助获取更新信息。 2.4 访问关系型数据库系统中的数据 SAS提供了一组访问关系型数据库的SAS/ACCESS接口,每种接口 有单独的许可。使用这些接口,SAS可以和其他厂商数据库中的数据交 互。SAS所支持的关系型数据库如表2.3所示。 表2.3 SAS支持的关系型数据库 除了上述关系型数据库外,SAS还提供了对应的SAS/ACCESS引擎 访问ERP、SPSS等系统软件中的数据。这些内容本书不做介绍,读者如 果有需要可以参考SAS帮助文档学习。 SAS/ACCESS接口引擎提供以下方法访问关系型DBMS中的数据: ·使用LIBNAME语句将SAS逻辑库引用名定义到DBMS对象,例如 schema和数据库。 ·使用SQL转交(pass-through)功能。通过该功能,在SAS会话中 可以使用原生SQL语法与数据源交互,这些SQL语句会直接交给数据源 处理。 还可以使用ACCESS过程来访问数据库系统,但是SAS不推荐使用 这种方式。SAS推荐使用更直接的方式访问DBMS数据,如上面提到的 两种。所以,本书不介绍ACCESS过程,感兴趣的读者可以通过SAS帮 助文档了解。 1.通过LIBNAME语句访问 本章第一节介绍SAS逻辑库时,已经提到了接口逻辑库引擎的概 念。接口逻辑库是通过SAS/ACCESS接口软件来访问的其他软件,例如 数据库管理系统、格式化的文件等。通过LIBNAME语句指定接口逻辑 库的引用名后,就可以像访问SAS原生数据集一样通过二级引用来访问 数据库中的表了。这时数据库中的表也称为接口数据集。这里简单介绍 SAS/ACCESS用LIBNAME语句访问关系型数据库的一般用法,更详细 的用法请查看SAS帮助文档。 注意 大多数情况下,接口数据集的使用与原生数据集没有区 别,但仍然会有些限制。例如在DATA步的DATA语句和SET语句中不 能指定同一个接口数据集,否则SAS会报错。 LIBNAME语句指定到DBMS对象的逻辑库引用名的基本形式如 下: LIBNAME 逻辑库引用名 逻辑库引擎 访问连接选项; 其中: ·逻辑库引用名为访问数据库的逻辑引用名称,在本章第一节介绍 SAS逻辑库时有详细介绍。 ·逻辑库引擎由所要访问的数据库确定,例如Oracle数据库的引擎为 oracle,Teradata数据库的引擎为teradata,Hadoop的引擎为hadoop。 ·访问连接选项提供连接信息并控制SAS如何管理到DBMS链接的时 机和并发。不同数据库,连接选项会不同。例如,连接到Oracle数据的 连接选项为User=、PASSWORD=和PATH=。 下面两条LIBNAME语句分解建立了到Teradata数据库和Oracle数据 库的逻辑库引用名。接着,就可以使用带逻辑库引用名tdlib和oralib的二 级名称引用数据库中的表了。 libname tdlib teradata server=tera2650 database=hps user=user1 password=password1; libname oralib oracle path=mypath schema=myschema user=usr1 password=password1; 2.通过PROC SQL访问 PROC SQL为SAS软件实现了结构化查询语句(Structured Query Language,SQL)。关于PROC SQL的信息在本书第6章中会详细讲解, 这里主要介绍如何通过PROC SQL使用SAS/ACCESS访问关系型数据 库。 ·使用LIBNAME语句指定接口逻辑库引用名,然后在PROC SQL语 句中引用该引用名查询、更新或删除DBMS数据。 ·将LIBNAME信息嵌入PROC SQL视图中,在每次处理该SQL视图 时会自动连接到DBMS。 ·使用PROC SQL的扩展功能,将DBMS特定的SQL语句直接发送到 DBMS,该功能叫作SQL转交(pass-through)功能。 前两种方法使用的仍然是SAS/ACCESS LIBNAME引擎,引用数据 库中表的形式与引用SAS原生数据集相同,这里不做介绍。作为 LIBNAME语句的替代,SQL转交功能使用SAS/ACCESS连接DBMS,并 将语句直接放到DBMS中执行,这样就可以使用DBMS本身的SQL语法 了。所以,SQL转交功能支持当前DBMS支持的任何非ANSI标准的 SQL。需要注意的是,不是所有的SAS/ACCESS接口都支持这种属性。 使用SQL转交功能的基本形式如下: PROC SQL; CONNECT TO 数据库名称 <AS 别名> <(<数据库连接参数> )>; EXECUTE (数据库特定SQL语句) BY 数据库名称|别名; SELECT 列列表FROM CONNECTION TO数据库名称|别名 (数据库查询); DISCONNECT FROM数据库名称|别名; QUIT; 其中: ·CONNECT语句建立到DBMS的连接。数据库名称标识要连接的数 据库管理系统;别名为该连接指定别名;数据库连接参数指定PROC SQL连接到DBMS需要的特定的DBMS参数。 ·EXECUTE语句发送DBMS特定的、非查询SQL语句到DBMS。 SAS会把输入的内容原封不动地发送到DBMS。有些DBMS可能是大小 写敏感的,需要注意。 ·CONNECTION TO组件获取并使用PROC SQL查询或视图中的 DBMS数据。数据库查询指定要发送到DBMS上的查询,该查询可使用 对该DBMS有效的任何DBMS特定的SQL语句或语法。同样,这些查询 对有些DBMS可能是大小写敏感的。 ·DISCONNECT语句终止与DBMS的连接。 各语句执行的返回值和消息保存在宏变量&sqlxrc和&sqlxmsg中。 下面的代码建立到Oracle数据库的连接。在CONNECT语句中, oracle为数据库名称,mycon为别名,括号里的内容为数据库的连接参 数。%put宏将上一条CONNECT语句的返回值和代码打印到日志窗口。 SELECT语句通过建立的连接将表employees中满足条件(hiredate>='31DEC-88')的行指定的5列(empid、lastname、firstname、hiredate、 salary)数据取出。 proc sql; connect to oracle as mycon (user=myusr1 password=mypwd1 path='mysrv1' schema=myshm1); %put &sqlxmsg; select * from connection to mycon (select empid, lastname, firstname, hiredate, salary from employees where hiredate>='31-DEC-88'); %put &sqlxmsg; disconnect from mycon; quit; PROC SQL还可以将上面的查询存储为SQL视图或创建为SAS数据 集。下面的代码中查询条件一样,还是将查询存储为SAS逻辑库中的 SQL视图,这样在下次使用该视图时就可以自动从数据库中获取数据 了。 libname samples 'SAS-library'; proc sql; connect to oracle as mycon (user=myusr1 password=mypwd1 path='mysrv1' schema='schm1'); %put &sqlxmsg; create view samples.hires88 as select * from connection to mycon (select empid, lastname, firstname, hiredate, salary from employees where hiredate>='31-DEC-88'); %put &sqlxmsg; disconnect from mycon; quit; 2.5 SAS程序错误及处理 通常我们所开发的SAS程序,很少在第一次提交时就能够运行完成 并产生正确结果。程序越长、越复杂,就越可能出现语法错误或逻辑错 误。本节介绍了一些良好的SAS编程规范以减少程序错误,同时也描述 了几种常见的错误及错误发生后的处理方法。 2.5.1 良好的SAS编程风格 在开发SAS程序的过程中,遵循下面几条规则可以减小程序出错的 几率,并有助于发现错误。 (1)提高程序的易读性 一个简单的方式是在开发程序时保持代码的整洁和一致。易读的程 序会更易于调试,长期来看会节省时间。在编写SAS程序时可遵循如下 建议以提高程序的易读性: ·每行一条SAS语句。SAS允许在一行中写多个SAS语句,这样会节 省代码空间,但是这些空间会以损失程序的易读性为代价。 ·程序的不同部分使用相应的缩进。缩进DATA步和PROC步中的所 有语句,这种方式会很容易得知程序中有多少个DATA步和PROC步, 并且知道那些语句属于哪个过程步。 ·代码中引用的所有SAS文件都使用二级名称,例如work.customer, 即使该SAS文件存储在WORK临时逻辑库或USER逻辑库中也应如此。 ·使用RUN或QUIT语句显式地表明DATA步或PROC步结束。虽然 SAS会在遇到下一个DATA步或PROC步时自动结束当前过程步,但是 RUN或QUIT语句会让程序块的逻辑更清楚。 ·使用注释说明程序代码段。给程序添加注释可能会花一些额外的 时间,但是很多时候注释很重要,尤其是当其他人查看或使用代码时。 (2)测试程序的每个部分 在开始写下一部分的代码之前,先测试前面已经完成的代码。保证 已经完成的代码运行正确会极大地提高开发效率。 如果是从外部数据文件读取数据到SAS数据集,则使用PROC PRINT打印SAS数据集或数据集的部分数据,以保证该数据集被正确生 成。有时,在日志中可能没有错误或可疑的提示,但所生成的数据集却 是不正确的。这是因为所开发的代码可能并没有如预期的那样读取数 据,或原始数据本身存在某些在开发代码时没有意识到的问题。对此, 好的习惯是,对程序创建的所有SAS数据集都至少要打印一次,以进行 检查。 (3)正式运行程序前使用子数据集测试程序 有时,使用全部数据测试程序是不现实的。如果数据文件特别大, 测试所有的数据会很费时,这时可以使用数据的子集来进行测试。 如果是从文件中读取数据,可以在INFILE语句中使用OBS=告诉 SAS读取文件中的前多少行,例如前50、前100行等,只要能代表要读 取的数据就行。下面的代码仅仅读取文件的前100行。 infile 'customer.dat' obs=100; 还可以使用选项FIRSTOBS=指定从文件的中间部分开始读取数 据。例如,下面的语句读取customer.dat文件的第101行到第200行。 infile 'customer.dat' firstobs=101 obs=200; 同样,选项FIRSTOBS=和OBS=也可以用于在SET语句中读取该数 据集中对应的观测。下面的代码会读取数据集saslib.customer中的第 101~200个观测。 set saslib.customer (firstobs=101 obs=200); 关于SET语句及数据集选项,本书下一章会详细介绍。 (4)使用语法敏感的编辑器 在Windows操作系统下,增强型编辑器(Enhanced Editor)是默认 的编辑器,在其他操作系统下,程序编辑器(Program Editor)是默认编 辑器。这两种编辑器都会对代码的不同部分自动添加颜色,例如SAS关 键字是一种颜色,变量是另一种颜色。此外,所有引号中的文本也是同 样的颜色,这样我们可以很容易区分是否存在引号不匹配的情况。类似 地,遗漏分号也很容易发现,因为遗漏分号可能会导致接下来的代码颜 色不正确。 2.5.2 常见错误及处理 SAS程序提交执行后会在日志窗口或日志文件中产生代码运行的信 息。我们可以根据日志信息确定什么时候程序发生错误,并获取信息纠 正错误。SAS通常会识别以下4类错误: ·语法错误。语法错误是在SAS语句中犯的错误,包括单词拼写错、 遗漏或无效的标点符号、无效的语句或数据集选项等。 ·执行时错误。当程序提交执行时,执行时错误会让程序失败。大 多数不严重的执行时错误会在SAS日志中产生提示消息,但是程序还能 继续运行。如果是更严重的错误,SAS会打印错误消息并停止处理。 ·数据错误。数据错误是一种执行时错误。当SAS程序正在分析的原 始数据包含无效值时就会发生数据错误。例如,INPUT语句中指定了数 字变量,而原始数据记录中的数据值是字符。数据错误不会引起程序停 止,但是会在SAS日志中产生提示信息。 ·语义错误。语义错误是另一种执行时错误,当SAS语句形式是正确 的,但有些选项或语句等的使用方式无效时会发生。例如,函数中指定 的参数个数错误、在只有字符变量有效的地方使用数字变量名或使用了 没有赋值的逻辑库引用名等。 SAS检测到错误时,通常会将错误或检测到错误的位置加下划线, 并显示一个数字。每个数字与一条错误消息唯一关联。接着SAS进入语 法检查模式,它会读取剩下的程序语句、检查语法,并在其他错误位置 加下划线。 在批处理或非交互式的程序中,DATA步中的错误会导致SAS对剩 下的程序一直处于语法检查模式,其他任何创建外部文件或SAS数据集 的DATA步或PROC步都不会执行。然而,读SAS数据集的过程会执 行,但是读入的观测数会为0,而不读SAS数据集的过程正常执行。通 常PROC步中的语法错误仅仅影响当前PROC步。在该PROC步结束时, SAS会将检测到的每个错误写入SAS日志。 1.语法错误 有些语法错误从日志窗口中很容易理解并改正,有时SAS还会自动 纠正并提示警告信息,例如在图2.50中展示的是关键字拼写错误。关键 字INPUT错误拼写为INYUT,SAS给出警告信息并将该拼错的词理解为 INPUT。这样,编译会通过,代码也会正确执行。但SAS并不负责将代 码中的错误改正,需要开发人员自己修改。 图2.50 关键字拼写错误 很多语法错误是由于遗漏分号(;)导致的,根据其在日志窗口的 消息,可能没那么容易找到出错原因。在下面的代码中,DATA语句指 定了数据集名称之后遗漏分号(;)。 data saslib.customer length Name $20 Address $40; infile extfiles(customer_dsd) dsd; input Customer_ID $ Name $ Address $; run; 提交代码执行时,因为遗漏分号,SAS认为DATA语句中没有结 束,length和Name都被当作数据集名。SAS无法解释接下来出现的$, 所以认为出错。SAS在日志中显示的信息如图2.51所示。在$出现的地方 加划线,给出期待在这个地方出现的名称或符号,并提示出错。 图2.51 遗漏分号 这时需要通过在DATA语句数据集名称之后添加分号(;)来改正 该段代码。 2.数据错误 在下面的程序中,INPUT语句使用列表方式读取数据值。要读取的 最后一个变量为数值型变量,但输入数据中第三行记录的最后一个字段 为字符。 data saslib.inventory; input Product_ID $ Instock Price; Cost=Price*0.15; datalines; P001R 12 125.00 P003T 34 40.00 P301M 23 five hundred PC02M 12 100.00 ; run; 提交执行后查看日志窗口信息,如图2.52所示。SAS提示在第10~13 列(因为是列表输入方式,所以要赋值给Price的是five,对应于第10~13 列)的数据对变量Price无效。为了便于分析和修正,SAS还在日志窗口 列出了输入缓冲区的值、PDV中各变量的值,包括DATA步中定义的变 量和自动变量_ERROR_和_N_等。该数据错误只是会导致所生成的观测 中存在缺失值,DATA步并不会停止执行。 图2.52 数据错误 对于这样的问题,有多种处理方式。在本例中可以不作处理,因为 它只会导致观测中存在缺失值。其他类似的情况,可以通过调整输入方 式,例如使用格式化输入等方法来解决。 3.语义错误 在下面的程序中,DATA步读取外部数据文件,并指定文件中字段 值之间的分隔符为逗号(,)。注意,其中INFILE语句的选项DLM=误 写为DLMA=了。 data saslib.Inventory; infile invtfile dlma=','; input Product_ID $ Instock Price; run; 提交程序执行后查看日志窗口的信息,如图2.53所示。SAS提示选 项名称DLMA无效。因为出错,SAS停止处理并创建没有观测值的数据 集。 图2.53 语义错误 这时需要更正该选项名称为DLM=,并再次提交执行。 4.引号不配对 在执行SAS程序时,有时会出现比较意外的情况。例如提交了代码 后,并不产生任何结果,日志窗口仅显示代码信息,没有对应的SAS语 句执行提示信息,不知道SAS在干什么,有时编辑器窗口会一直表示 PROC步正在运行。这时首先需要检查是不是存在代码中引号不配对的 问题。 在下面的代码中,FILNAME语句指定文件引用时,文件名结束后 遗漏了对应的单引号(')。 filename customfl 'c:\sas\data\customer_dsd.dat; data saslib.customer length Name $20 Address $40; infile customfl dsd; input Customer_ID $ Name $ Address $; run; proc print data=saslib.customer noobs; run; 提交代码执行,并查看日志窗口,有原始代码,但没有语句执行提 示信息,如图2.54所示。 图2.54 引号不配对 遇到的这样的问题,建议先提交下列代码将引号配对,从而使程序 完成执行过程,再来检查修正程序的错误。这3行代码除了可以解决单 引号和双引号不匹配的问题以外,还可以解决注释标记不匹配,以及遗 漏分号、QUIT或RUN语句的问题。 /* '; * "; */; quit; run; 本章小结 2.6 本章首先介绍了SAS编程的基本概念,具体包括SAS逻辑库、SAS 数据集、SAS数据集管理、系统选项以及SAS程序结构等内容;在此基 础上介绍了DATA步处理数据的原理,重点讲解了如何使用DATA步的 各种输入方式来读取不同格式的外部文件中的数据,并创建数据集;接 下来介绍了如何运用IMPORT过程读取外部的数据文件,以及如何通过 LIBNAME语句和PROC SQL访问关系型数据集库系统中的数据;在本 章的最后,还介绍了SAS程序开发中常见的几种错误现象及其处理方 法。 第3章 对单个数据集的处理 在DATA步中可以使用SET、MERGE、MODIFY或UPDATE语句对 数据集进行加工处理。在对数据集进行处理前,可以使用SAS窗口环境 的VIEWTABLE窗口、PRINT过程,或者PROC CONTENTS(或PROC DATASETS的CONTENTS语句)对数据集进行查看,了解其基本情 况,比如,包含多少个观测、多少个变量、变量名称、类型等。 本章主要介绍如何使用SET语句对单个数据集进行操作,例如读取 部分变量、读取部分观测、修改变量值、生成新变量,以及对数据集中 的观测排序分组等。至于在DATA步中使用MERGE、MODIFY和 UPDATE语句,以及SET语句对多个数据集的处理会在第4章介绍。在 本章后面还将介绍SAS循环语句、数组、在开发SAS程序过程中常用的 对数据值进行处理的函数,以及如何将SAS数据集中的数据导出到外部 文件。 选取部分变量 3.1 在使用DATA步基于已经存在的数据集生成新数据集时,可以指定 在新数据集中不需要包含的变量而仅读取其他变量,或者指定仅需要在 新数据集中包含的变量。该功能可以通过DATA步中的SET语句和数据 集选项KEEP=和DROP=来实现,也可以通过KEEP和DROP语句来实 现。 1.使用数据集选项KEEP=和DROP= 使用数据集选项KEEP=和DROP=的基本形式如下: DATA 新数据集; SET 原数据集 (KEEP | DROP=变量列表); RUN; 在该过程中,DATA步通过读取原数据集的部分变量来建立新数据 集。新数据中包含的变量由所使用的选项(KEEP=或DROP=)给出的 变量列表确定。使用选项KEEP=表示只读取变量列表中的变量,而使用 选项DROP=则表示读取除变量列表中列出的变量之外的其他所有变 量。 例3.1:读取数据集sashelp.shoes中与产品销售相关的变量Product、 Stores和Sales,建立新数据集。 代码如下: data work.shoes_part1; set sashelp.shoes (keep=Product Stores Sales); run; proc print data=work.shoes_part1 (obs=5) noobs; run; 在上面的代码中,SET语句使用数据集选项KEEP=指定的Product、 Stores和Sales。PROC PRINT打印所生成的数据集work.shoes_part1的前5 条观测,如图3.1所示。可以看到,该数据集中包含了变量Product、 Stores和Sales。 图3.1 例3.1打印输出的数据集内容 下面使用数据集选项DROP=来实现相同的功能,代码如下: data work.shoes_part2; set sashelp.shoes (drop=Region Subsidiary Inventory Returns); run; 因为sashelp.shoes的变量包含Product、Stores、Sales、Region、 Subsidiary、Inventory和Returns,所以当选项DROP=中指定了Region、 Subsidiary、Inventory和Returns时,剩下的变量Product、Stores和Sales都 会被读取并写入数据集work.shoes_part2。所生成的数据集 work.shoes_part2和前面示例中生成的work.shoes_part1相同。 简单来说,选择使用选项KEEP=还是DROP=依赖于哪种方法会需 要指定较少的变量。但相比较而言,使用选项KEEP=会明确指明需要读 取的变量,这样在比较大的作业中可以避免读取预期之外的变量。 2.使用KEEP和DROP语句 在DATA步中,KEEP和DROP语句同样可用于选取写入到新数据集 中的变量。使用DROP和KEEP语句的基本形式如下: DATA 新数据集; SET 原数据集; KEEP | DROP变量列表; RUN; 在该过程中,DATA步会读取原数据集的所有变量,但在写入新数 据集前只保留部分变量。新数据集中包含的变量由所使用的语句 (KEEP语句或DROP语句)给出的变量列表确定。使用KEEP语句表示 只选取变量列表中变量,而使用DROP语句则表示选取除变量列表之外 的其他所有变量。 例3.2:读取数据集sashelp.shoes中跟产品销售相关的变量Product、 Stores和Sales,建立新数据集。 下面两段代码分别使用KEEP语句和DROP语句来完成上述功能,生 成数据集work.shoes_part3和work.shoes_part4。 代码1: data work.shoes_part3; set sashelp.shoes; keep Product Stores Sales; run; 代码2: data work.shoes_part4; set sashelp.shoes; drop Region Subsidiary Inventory Returns; run; 在代码1中,DATA步会读取数据集sashelp.shoes中的所有变量,并 选取KEEP语句指定的Product、Stores和Sales创建新数据集 work.shoes_part3。在代码2中,DATA步的DROP语句指定了Region、 Subsidiary、Inventory和Returns,那么剩下的变量Product、Stores、Sales 会被选取写入数据集work.shoes_part4。这里所生成的数据集 work.shoes_part3和work.shoes_part4也和work.shoes_part1相同。 使用KEEP语句还是DROP语句,与选择使用数据集选项KEEP=还 是DROP=的标准一样。从上面的示例中可以看出,它们都可以实现相 同的功能。那么,在开发程序时具体该怎样选择呢? 先来理解一下它们在执行过程中的不同之处。在DATA步中,SET 语句会将数据读入程序数据向量PDV中。在SET语句中使用数据集选项 时,只有选取的变量才会被读入PDV中,最后写入新数据集。而使用 KEEP和DROP语句来选取变量时,SET语句会将原数据集中的所有变量 读入PDV中,然后再通过相应的KEEP和DROP语句进行选取并写入新数 据集。所以数据集选项KEEP=和DROP=会控制读入程序数据向量PDV 中的变量,使用数据集选项通常会更有效率。 另外,有些功能可以通过数据集选项KEEP=和DROP=实现,但 KEEP和DROP语句不能实现,接下来会介绍。 3.一个DATA步中创建多个数据集 数据集选项KEEP=和DROP=除了可以在SET语句中使用之外,还可 以用于DATA语句中指定的数据集。这样就可以在一个DATA步中通过 给每个数据集使用选项KEEP=和DROP=来创建包含不同变量的多个数 据集。而KEEP和DROP语句却实现不了该功能,因为它们会影响所有的 输出数据集。 例3.3:分别读取数据集sashelp.shoes中关于产品销售情况的变量 (Product、Stores及Sales)和产品库存情况的变量(Product、Inventory 及Returns),并将这两类变量写入两个数据集work.shoes_sales和 work.shoes_inventory。 代码如下: data work.shoes_sales (keep=Product Stores Sales) work.shoes_inventory (keep=Product Inventory Returns); set sashelp.shoes; run; proc print data=work.shoes_sales (obs=5) noobs; title "Product Sales"; run; proc print data=work.shoes_inventory (obs=5) noobs; title "Product Inventory"; run; 两个PRINT过程打印的数据集work.shoes_sales和 work.shoes_inventory中的前5条数据分别如图3.2和图3.3所示。它们分别 包含变量Product、Stores、Sales和Product、Inventory、Returns。 图3.2 例3.3输出的产品销售信息 图3.3 例3.3输出的产品库存信息 4.有效地使用数据集选项KEEP=和DROP= 在DATA步中,可在DATA语句和SET语句中使用数据集选项 KEEP=和DROP=。在DATA语句使用这些选项,PDV中会包括输入数据 集中的所有变量,不过,只有当变量从PDV中写入结果数据集时,这些 选项才会产生影响。然而,在SET语句中使用这些选项时,这些选项会 确定哪些变量要从输入数据集中读取到PDV中,也就是说,SAS不会将 未包括的变量读入PDV。在数据集很大时,这种方式会使程序执行更有 效率。 在上面代码的基础上可在SET语句中增加选项DROP=来控制变量 Region和Subsidiary不被读入PDV,以提高程序执行效率。如下: data work.shoes_sales (keep=Product Stores Sales) work.shoes_inventory (keep=Product Inventory Returns); set sashelp.shoes (drop=Region Subsidiary); run; 有时候,部分变量虽然不需要输出到新数据集,但在进行运算处理 时却需要用到,这时候这些变量必须被读入PDV中,那么此时就不适合 在SET语句中使用数据集选项KEEP=和DROP=了。对于这种情况,可在 DATA语句中使用数据集选项KEEP=、DROP=,或使用KEEP、DROP 语句来实现。 操作数据集的观测 3.2 在对数据集进行操作时,通常会需要对满足条件的特定观测进行操 作,例如修改变量值等,这时需要用到条件语句、赋值语句,以及表达 式。本节首先介绍SAS表达式,接着讲解如何使用表达式结合条件语句 和赋值语句来对SAS数据集的观测进行操作,最后介绍使用SORT过程 对数据集排序,以实现对观测进行分组。此外,还简单地介绍如何对单 个分组数据进行操作。分组数据在对多个SAS数据集进行加工时会有更 为丰富的使用,这部分内容在第4章中进行介绍。 3.2.1 SAS表达式 表达式是操作数和操作符的序列,该序列会形成一组可执行并产生 结果值的指令。其中,操作数可以是常量、变量或表达式;操作符是表 示比较、数学计算或逻辑运算的符号,也可以是SAS函数或者括号组。 在SAS程序语句中,创建变量、赋值、求新值、转换变量和执行条件处 理都会用到表达式。 表达式的例子如下:3、x、x+1、age<10、trim(last)||','||first。 SAS表达式的结果值是数字值、字符值或布尔值。 1.操作数 操作数可以是常量、变量或表达式。SAS常量是表示一个固定值的 数字或字符串。常量可用作许多SAS语句的表达式,包括变量赋值语句 和IF-THEN语句,还可作为特定选项的值,例如OBS=5。SAS中存在4 类常量:字符常量、数字常量、时间日期常量和位测试常量。 (1)字符常量 字符常量由1到32767个字符组成,并且必须放在引号(单引号或双 引号)中。在下面的SAS语句中,Tom是一个字符常量。 if name='Tom' then do; 如果字符常量包括单引号,则将该常量放入双引号中。例如,为了 指定字符值Tom's,使用下面的形式: if name="Tom's" then do; 或者将字符串放入单引号,并且用两个连续的单引号表示撇号。 SAS将两个连续的引号作为一个引号。例如,要表示字符串Tom's,则 使用下面的形式: if name='Tom''s' then do; 要表示Tom“s,可以使用以下形式: if name="Tom""s" then do; 注意 使用引号一定要匹配,否则会致使SAS误读当前的错误语 句以及跟随其后的语句。 字符常量还可以以十六进制形式表示。字符的十六进制常量是一个 在单引号或双引号中偶数位的十六进制字符表示的字符串,并且该字符 串后紧随字母X,例如'546F6D'x表示字符串“Tom”。引号中如果有前导 或尾缀空格,则会出错,错误信息会写入日志中。可以使用逗号使引号 中的字符串更可读。如果使用逗号,则逗号必须分隔该字符串的偶数个 十六进制字符,例如'54,6F6D'x。 (2)数字常量 数字常量指的是SAS语句中出现的数字值。数字常量可以表示为标 准计数法、科学计数法和十六进制计数法,例如1、-5、+49、1.23、 01、2E23、0.5e-10等。对于大于1032-1的数字常量,必须使用科学计数 法。 数字常量也可以以十六进制值表示。该十六进制值以数字(通常是 0)开始,接着是多个十六进制字符,以字母X结束。常量最大可包含 16个有效的十六进制字符(0~9、A~F)。例如,0c1x、9x。 (3)时间日期常量 在SAS中还可以创建日期常量、时间常量、时间日期常量。这些常 量的形式为包含在单引号或双引号中的指定日期或时间,并接着跟随一 个d(日期)、t(时间)或dt(日期时间)来说明值的类型。例 如,'1jan2013'd、'9:25't、'01may12:9:30:00'dt。任何引号中包括的 前导或尾缀空格都不会影响这些常量的处理。 (4)位测试常量 位测试常量是在引号中由0、1和点组成的字符串,而且其后紧跟 b,例如'..1.0000'b。0用于测试该位是否为0,1用于测试该位是否为1, 点(.)则表示忽略该位的测试。逗号和空格可插入位掩码中增加可读 性。在位测试中,位常量用于对字符或数字变量进行位比较。当测试字 符值时,SAS会将掩码的最左位与字符串的最左位对齐,测试则从左往 右逐位处理对应位。当测试数字值时,数字值会从浮点数截断为32位整 型,SAS将掩码的最右位与值的最右位对齐,然后往左逐位处理对应 位。 下面的例子用于测试字符型变量a: if a='..1.0000'b then do; 如果a(从左开始计数)第3位为1,并且第5~8位都是0,则该比较 为真,并且表达式a='..1.0000'b的结果为1;否则,比较为假,表达式值 为0。 位掩码不可以用作赋值语句中的位值。例如下面的语句是无效的: x='0101'b; /* incorrect*/ $BINARYw.和BINARYw.格式,以及$BINARYw.、BINARYw.d和 BITSw.d输入格式在位测试中非常有用。可以将字符和数字值转换为对 应的二进制值进行位测试。 (5)变量 变量是一组描述给定特性的数据值,可用于表达式中。 如果在一个表达式中指定了变量,但是变量值不匹配需要的类型, 例如,在需要数值变量的地方使用了字符变量,或者相反,在需要字符 变量的地方使用了数值变量,SAS则会尝试将该变量值转换成所期望的 类型。SAS会按照如下规则自动在字符变量和数值变量之间转换: ·如果使用要求数字操作数的操作符(例如加号+)时指定了字符变 量,SAS将字符变量值转换为数字。 ·在使用比较操作符比较字符变量和数值变量时,SAS会将字符变量 值转换为数字。 ·如果使用要求字符操作数的操作符(例如级联操作符)时指定使 用了数值变量,SAS使用格式BEST12.将数值变量值转换为字符。关于 格式BEST12.,请参考SAS帮助文档。 ·如果在赋值语句的左侧使用了数值变量而右侧是字符变量,SAS会 将字符变量值转换为数字。反之,当左侧是字符变量而右侧是数值变量 时,SAS会使用格式BESTn.将数值变量值转换为字符。其中,n是左侧 变量的长度。 当执行自动转换时,SAS会在日志中打印提示信息,表明发生了转 换。如果将字符变量值转换成数字时产生了无效的数字值,那么表达式 的结果是缺失值,并且会在日志窗口打印错误消息,同时,会将自动变 量_ERROR_设置为1。 还可以通过PUT和INPUT函数转换数据值。这些函数比自动转换会 更有效,本章后面的章节会介绍。 2.操作符 操作符包含算术操作符、比较操作符、逻辑操作符等,分别用于算 术运算、比较表达式和对布尔值进行操作等。此外,它还提供了一些只 能用于WHERE语句或WHERE=选项的操作符。 (1)算术操作符 使用算术操作符的表达式其运算结果是数值。表3.1给出了算术操 作符的定义、示例及示例表达式的计算结果。 表3.1 算术操作符号 (2)比较操作符 使用比较操作符的表达式其运算结果是真(1)或假(0)。表3.2 给出了SAS的比较操作符、等效字符、说明及使用该操作符的例子。 表3.2 注意 比较操作符 *NE的符号依赖于当前键盘上的字符,可能为^=、=、或 ~=。 **>=和<=用于与以前SAS版本兼容。在WHERE从句或PROC SQL 中不支持。 在对数字值进行比较时,SAS会基于值进行比较。缺失数字值小于 任何其他数字值。表达式为真时,表达式的结果是1或真(true);表达 式为假时,表达式的结果是0或假(false)。比较操作符常用于IFTHEN语句中,如以下例子: if x<y then c=5; else c=12; 也可在赋值语句表达式中使用比较,例如: c=5*(x<y)+12*(x>=y); 这时,SAS先计算括号内表达式(x<y)和(x>=y)的值(为0或 1),然后使用计算结果替代括号里的表达式。因此,假设x=6,y=8, 那么赋值语句c=5*(1)+12*(0)的结果是c=5。 记住,比较不同长度的数字值时可能会产生不正确的结果,因为小 于8字节的值比8字节的值有更小的精度。另外,四舍五入也会影响数字 比较的输出。 字符操作数的比较也会产生数字值1(或真)或0(或假)。SAS会 从左至右逐个字符对字符操作数进行比较。空格和缺失值小于其他任何 可打印字符值。字符顺序依赖于计算机的排列顺序,此顺序通常指的是 在ASCII或EBCDIC编码中的顺序。例如在EBCDIC和ASCII的排列顺序 中,G大于A。因此,表达式'Gray'>'Adams'的值为1或真。 如果是不同长度的两个字符值进行比较,在比较之前,SAS会假设 已经用空格补充到了较短的字符操作数结尾处,使两个字符值有了相同 的长度。在比较中尾缀空格会忽略,所以'fox'等于'fox'。然而,在字符 值开始处和中间的空格都会参与比较,所以,'fox'不等于'fox'。 还可以在比较操作符之后使用“:”来比较字符表达式的指定前缀。 SAS会在比较过程中截断较长的值使其与较短值的长度一致。在下面的 例子中,在等于符号后面的冒号修改器告诉SAS仅查看变量LastName的 第一个字符是否为S。 if lastname=:'S'; (3)逻辑(布尔)操作符 使用逻辑操作符的表达式其运算结果是布尔值,即为真(1)或假 (0)。表3.3给出SAS的逻辑操作符、等效字符、示例及其说明。 表3.3 逻辑操作符 注意 *OR的符号取决于当前操作环境,可能为竖线(|)、断开 的竖线(|)或叹号(!)。 **NOT的符号取决于当前操作环境,可能为、^、~。 (4)其他操作符 在SAS表达式中还可以使用一些其他操作符,例如级联操作、括号 等,如表3.4所示。 表3.4 注意 级联、括号及正负数操作符 *级联符号取决于当前操作环境。 在表达式,特别是包含多个操作符的复合表达式中,经常会将一些 子表达式放入括号()中,表示优先对括号中的表达式求值,同时也会提 高表达式的易读性。 级联操作符||会将操作符两侧的字符值进行级联。通常会使用赋值 语句将级联操作的结果存储在结果变量中。如果事先没有通过LENGTH 或ATTRIB语句指定该结果变量的长度,则其长度为在级联操作中每个 变量或常量的长度总和。 级联操作不会去除操作数的前导和尾缀空格。如果变量带尾缀空 格,在级联前使用TRIM函数可去除值中的尾缀空格。如果要去除操作 数的前导和尾缀空格,通常使用表达式TRIM(LEFT(char))。 (5)WHERE语句操作符 在WHERE语句或数据集选项WHERE=中使用的表达式称为 WHERE表达式。在WHERE表达式中除了可以使用上述操作符之外,还 可以使用如表3.5所示的操作符。注意,这些操作符只能在WHERE表达 式中使用。 表3.5 WHERE表达式可用的操作符 关于这些操作符的详细使用情况,请参考SAS帮助文档。 (6)MIN、MAX操作符 MIN(><)和MAX(<>)操作符分别用于找到两个操作数中的最 小值和最大值。例如,如果A<B,那么A><B的返回值为A,A<>B的返 回值为B。如果比较中包含缺失值,SAS使用缺失值的排序顺序。 注意,在WHERE语句或WHERE从句中,<>操作符等同于NE。 3.复合表达式求值 仅包含一个操作符的表达式为简单表达式。为了表示复杂的逻辑或 操作,表达式中通常会包含多个操作符,这样的表达式称为复合表达 式。复合表达式中经常使用括号对操作数进行分组。当遇到复合表达式 时,SAS会遵循下述规则确定计算表达式各部分的顺序: ·如果复合表达式中有括号,SAS会先对在括号中的表达式求值,再 对括号外的表达式求值。 ·表3.6中给出了不同组的优先级。SAS先对组Ⅰ中的表达式部分求 值,然后依次对组Ⅱ、组Ⅲ等组求值。 ·表3.6中也给出了同组内的求值顺序。对组Ⅰ中的操作符是从右到 左,而其他组的操作符都是从左到右。 表3.6 复合表达式求值顺序 ①NOT、NE、OR以及组IV中的级联符号取决于当前操作环境。 3.2.2 选取部分观测 在建立新数据集时,有以下两种方式可以从已经存在的数据集中选 取观测到新数据集中。 ·通过删除不满足条件的观测来保留想要的观测。 ·仅接受满足条件的观测。 条件可以由IF语句、WHERE语句或数据集选项WHERE=中的条件 表达式来指定。WHERE语句和数据集选项WHERE=可以用在DATA步 和PROC步中,两者的使用方法基本相同,本书第5章会介绍它们在 PROC步中的使用。本节主要介绍如何使用各种IF条件语句,以及同时 使用IF-THEN/ELSE语句和OUTPUT语句,将已经存在的数据集中的观 测根据给定条件一次或多次写入一个或多个输出数据集中。 1.使用DELETE语句删除满足条件的观测 在DATA步中可以结合使用IF语句和DELETE语句来删除满足条件 的观测,其基本形式如下: IF 条件表达式 THEN DELETE; 在该过程中,SAS首先判断条件表达式是否为真。如果为真,则执 行THEN从句中的DELTET语句。DELETE语句会让SAS立即返回DATA 步的开始处读取下一条观测,当前观测不会写入输出数据集中。注意, DELETE语句不会删除输入数据集中的观测。 例3.4:在数据集saslib.contact中选取地址信息(Address)不为缺失 值的观测建立新数据集work.contact_address。 已经存在的数据集saslib.contact中包含客户联系信息,内容如图3.4 所示。 图3.4 例3.4输入数据集的内容 在DATA步中IF语句用于判断Address信息是否为缺失值,DELETE 语句则可将Address为缺失值的观测删除。代码如下: data work.contact_address; set saslib.contact; if address = "" then delete; run; proc print data=work.contact_address; run; PRINT过程打印的数据集work.contact_address如图3.5所示。其中不 包含编号(Customer_ID)为C004的客户信息,因为其地址信息为缺失 值。 图3.5 例3.4打印的输出数据集内容 2.使用取子集的IF语句接受满足条件的观测 选择满足条件的观测另一种方式是直接选取满足条件的观测,即使 用取子集的IF语句(Subsetting IF),其基本形式如下: IF 条件表达式; 当条件表达式的值为真时,继续处理该观测;否则停止处理该观测 并返回DATA步开始处读取下一条观测,且该观测不会写入输出数据 集。该IF语句称为选取子集的IF语句,是因为所产生的输出数据集是原 始数据集的子集。 例3.5:选取数据集saslib.contact中其职位(Position)为Manager的 观测,建立新数据集work.contact_manager,并打印新生成的数据集中的 数据。 代码如下: data work.contact_manager; set saslib.contact; if position = "Manager"; run; proc print data=work.contact_manager noobs; run; PRINT过程打印的数据集数据如图3.6所示。 图3.6 例3.5打印的输出数据集内容 当DELETE语句和取子集的IF语句都可以完成相同的操作时,选择 哪种方式呢?主要依据如下: ·由于DELETE语句和取子集的IF语句需要构造的条件表达式不同, 因此在编写程序的时候,通常选择需要较少比较次数的条件表达式的语 句,因为这会提高程序执行效率。 ·在比较次数相近时,通常选择正向的条件表达式,也就是说选择 取子集的IF语句。 ·当数据中存在缺失值或可能有拼写错误的数据值时,使用取子集 的IF语句也更容易产生需要的结果。 3.使用OUTPUT语句建立多个数据集 结合条件语句和OUTPUT语句可以在一个DATA步中创建多个SAS 数据集,以便分别包含输入数据集的不同观测。使用OUTPUT语句的基 本形式如下: OUTPUT <数据集>; OUTPUT语句将当前观测写入指定数据集。其数据集必须为在 DATA语句中出现的数据集。当未指定数据集时,SAS会将当前观测写 入DATA语句的所有数据集中。 例3.6:选取在数据集saslib.contact中选取职位(Position)为 Manager的观测,建立新数据集work.contact_manager,对其他职位的观 测建立新的数据集work.contact_others。 代码如下: data work.contact_manager work.contact_others; set saslib.contact; if position = "Manager" then output work.contact_manager; else output work.contact_others; run; proc print data=work.contact_manager noobs; title "Postion is Manager"; run; proc print data=work.contact_others noobs; title "Postition is not Manager"; run; PRINT过程语句打印的数据集数据如图3.7所示。 图3.7 例3.6打印的输出数据集内容 如果在DATA步中不使用任何OUTPUT语句,在每次迭代结束时会 自动将观测写入DATA语句指定的所有数据集中。但是,如果在DATA 步中使用了OUTPUT语句,每次迭代结束时就不会自动将观测写入任何 数据集。因此,一旦在程序使用了OUTPUT语句,则必须为所有需要写 入数据集的观测使用OUTPUT语句。此外,还需要注意的是,如果在 OUTPUT语句之后进行计算,生成或改变的变量值也不会写入输出数据 集。 SAS在执行完OUTPUT语句后,观测会一直存在于PDV中,直到本 次迭代结束。所以在一次迭代中可以多次使用OUTPUT语句将PDV中的 观测写入一个或多个数据集。 例3.7:在saslib.contact中选取职位(Position)为Manager的观测输 出到数据集work.contact_manager中,并将年龄(Age)大于等于35的联 系人的观测输出到数据集work.contact_agege35中。 代码如下: data work.contact_manager work.contact_agege35; set saslib.contact; if position = "Manager" then output work.contact_manager; if age >= 35 then output work.contact_agege35; run; proc print data=work.contact_manager noobs; title "Position is Manager"; run; proc print data=work.contact_agege35 noobs; title "Age is older than 35"; run; PRINT语句打印的两个数据集内容如图3.8所示。其中Customer_ID 为C001的观测同时存在于两个数据集work.contact_manager和 work.contact_agege35中,因为其同时满足两个IF语句的条件。 图3.8 3.2.3 例3.7打印的输出数据集内容 操作所选取的观测 除了将满足条件的观测值直接写入结果数据集外,在DATA步中还 可以操作满足条件的观测中的变量值,例如修改变量名字以及生成新变 量等。 1.IF-THEN/ELSE语句 SAS选取观测进行操作时,最常用的方式是通过IF-THEN/ELSE语 句。其基本形式如下: IF 条件表达式 THEN 可执行语句; <ELSE 可执行语句;> 其中: ·条件表达式是一个或多个SAS表达式,通常为由比较操作符和操作 数组成的表达式。 ·SAS会对条件表达式的求值,结果为真(true)时,执行THEN从 句中的可执行语句;条件表达式的值为假(false)时,SAS忽略THEN 从句。可执行语句必须是在DATA步的单次迭代中可以执行的SAS语 句。最常用的可执行语句是赋值语句。 ·ELSE从句提供对该观测的可选操作。当条件表达式为真(true) 时忽略该从句;为假(false)时,执行ELSE从句中指定的语句。ELSE 从句可以不存在,如果存在则必须紧跟在对应的IF-THEN语句之后。 例3.8:在saslib.inventory中包含了产品在各地区的库存和价格信 息。公司现决定将每种产品在北京的销售价格提高20%,其他地区保持 不变。 代码如下: data work.Inventory2; set saslib.Inventory; if Region="BJ" then Price=Price*1.2; run; proc print data=work.Inventory2 noobs; run; PRINT过程打印价格提高后的数据集内容如图3.9所示。 例3.9:公司决定将每种产品在北京的销售价格提高20%,其他地区 销售价格提高10%。 这时可使用ELSE从句,代码如下: data work.Inventory3; set saslib.Inventory ; if Region="BJ" then Price=Price*1.2; else Price=Price*1.1; run; proc print data=work.Inventory3 noobs; run; PRINT过程打印价格提高后的数据集内容如图3.10所示。 图3.9 例3.8打印的输出数据集内容 图3.10 例3.9打印的输出数据集内容 2.赋值语句 赋值语句是可执行语句,常用于对变量进行赋值。其基本形式为: 变量=表达式; 其中: ·变量是已经存在的变量或新变量,可以是单个变量名、数组引用 或左侧的SUBSTR函数(即SUBSTR函数出现在赋值操作符左侧)等。 若变量已经存在,赋值语句会修改该变量的值;如果变量不存在,赋值 语句会创建新变量。 ·表达式是任何SAS表达式。 赋值语句用于对等号(=)右侧的表达式求值,并将结果存储在等 号(=)左侧的变量中。表达式中可包含出现在等号左侧的变量。这 时,变量的原始值用于对表达式求值,并且结果存储在等号左侧的变量 中。 在例3.8中,使用了赋值语句(Price=Price*1.2;)提高产品价格。 在处理第一条观测时,输入数据集中的Price值为125,对表达式 Price*1.2求值,结果为150,因为左侧变量为Price,所以结果仍然存储 在Price中。因此,输出数据集中Price的值为150。 3.DO语句 DO语句也是可执行语句。通过DO语句可以将一组可执行语句指定 为一个单元来执行。DO语句的基本形式如下: DO; …可执行语句组…; END; 在DO语句和END语句之间的语句称为DO组(DO Group)。DO语 句也可以嵌套DO语句。在IF-THEN/ELSE语句中,简单的DO语句通常 用于根据IF条件是否为真来指定要执行的一组可执行语句。 例3.10:基于数据集saslib.Inventory中的数据,将每种产品在北京的 销售价格提高20%,库存增加1倍,其他地区的销售价格提高10%,库存 增加50%。 这时在IF语句THEN从句和ELSE从句中使用DO语句,代码如下: data work.Inventory4; set saslib.Inventory; if Region="BJ" then do; Price=Price*1.2; Instock=Instock*2; end; else do; Price=Price*1.1; Instock=Instock*1.5; end; run; proc print data=work.Inventory4 noobs; run; PRINT过程打印价格所生成的数据集内容如图3.11所示。 图3.11 例3.10打印的输出数据集内容 4.嵌套IF-THEN/ELSE语句 一条IF-THEN/ELSE语句可根据条件提供两种不同的可选操作。很 多时候需要根据一系列互斥的条件执行不同的操作,这时可使用嵌套的 IF-THEN/ELSE语句。两层嵌套的IF-THEN/ELSE语句的基本形式如下: IF 条件表达式1 THEN 可执行语句1; ELSE IF条件表达式2 THEN可执行语句2; ELSE可执行语句3; 其执行过程如下: 1)SAS计算条件表达式1的值。 2)当条件表达式1的结果为真时,SAS执行可执行语句1,并忽略 紧跟其后的ELSE从句,包括嵌套的IF-THEN/ELSE语句。所有执行过程 完成。 3)当条件表达式1的结果为假时,忽略紧跟其后的THEN从句,并 进行下一步。 4)判断SAS条件表达式2的值。 5)当条件表达式2的结果为真时,SAS执行可执行语句2,并忽略 紧跟其后的ELSE。所有执行过程完成。 6)当条件表达式1的结果为假时,忽略紧跟其后的THEN从句, SAS执行可执行语句3。所有执行过程完成。 例3.11:基于数据集saslib.Inventory中的数据,公司决定将每种产品 在北京的销售价格提高20%,在上海的销售价格提高15%,其他地区的 销售价格提高10%。 使用嵌套的IF-THEN/ELSE语句进行处理的代码如下: data work.Inventory5; set saslib.Inventory; if Region="BJ" then Price=Price*1.2; else if Region="SH" then Price=Price*1.15; else Price=Price*1.1; run; proc print data=work.Inventory5 noobs; run; PRINT过程打印各地区价格提高后的数据集内容如图3.12所示。 图3.12 例3.11打印的输出数据集内容 5.SELECT语句 还可以通过SELECT语句构造不同的条件来操作观测。SELECT语 句的基本形式如下: SELECT <select-表达式>; WHEN when-表达式 可执行语句; <…WHEN when-表达式 可执行语句; > <OTHERWISE 可执行语句;> END; 其中: ·select-表达式指定计算单个值的SAS表达式。 ·when-表达式为任意SAS表达式。在SELECT语句和END语句之间 的语句称为SELECT组。SELECT组中要求至少有一条WHEN语句,而 WHEN语句中要求至少有一个when-表达式。when-表达式为真时,执行 跟随其后的可执行语句;否则,忽略其后可执行语句。 ·可执行语句可以是任何可执行的SAS语句,包括赋值语句、DO语 句、SELECT语句或空语句等。空语句用于WHEN语句中时,SAS会认 为该条件为真,但是不做任何操作。 ·OTHERWISE从句中指定当所有的WHEN条件都不满足时需要执行 的语句。 例3.12:同例3.11一样,将公司每种产品在北京的销售价格提高 20%,在上海的销售价格提高15%,其他地区的销售价格提高10%。 使用SELECT语句的代码如下: data work.Inventory6; set saslib.Inventory; select (Region); when ("BJ") Price=Price*1.2; when ("SH") Price=Price*1.15; otherwise Price=Price*1.1; end; run; proc print data=work.Inventory6 noobs; run; PRINT过程打印数据集work.Inventory6的内容与work.Inventory5一 样,这里不再给出。 3.2.4 分组与排序 SAS对数据集进行操作时,经常需要在SET、MERGE、MODIFY或 UPDATE语句中使用分组数据。使用分组数据最基本的方法是使用BY 语句,其基本形式如下: BY 变量列表; BY语句除了可用于DATA步中对数据集进行操作外,也可以用于 SAS PROC步。在这些地方使用分组数据时,要求所有的观测必须按BY 语句中的变量以数字或字符顺序升序或降序排列,或者以某种方式分 组,例如以日历的月份或格式化后的值为条件进行分组。如果数据不满 足这个条件,可使用SORT过程对其进行排序分组。 本小节会介绍如何对数据集中的变量排序,以使数据集符合分组需 要,以及在处理单个数据集时分组数据的运用。本书第4章也会介绍分 组数据的使用。 1.使用SORT过程对观测进行排序 使用SORT过程的基本形式如下: PROC SORT DATA=输入数据集 <OUT=输出数据集> <其他选项>; BY 变量列表; RUN; 其中: ·输入数据集指定需要排序的数据集。 ·选项OUT=指定存储排序后数据的新数据集。当该选项不存在时, 排序生成的数据写入由选项DATA=指定的数据集。输出数据集可以和 输入数据集相同,不过此时会覆盖输入数据集。当输出数据集与输入数 据集不同时,会创建新数据集。 ·还可以指定其他选项。常用选项如表3.7所示。 表3.7 SORT过程常用选项 ·变量列表指定排序变量,可以是一个变量或多个变量。当指定多 个变量时,SAS首先会按照第一个变量分组,然后在同一个分组内依照 变量列表中的其他变量逐个进行排序。 例3.13:对公司员工先按照部门(Dept)名称进行排序,在同一部 门里按照入职日期(Entry_Date)进行排序。 公司员工所在数据集saslib.employee的部分数据如图3.13所示。该数 据集包含员工编号、姓名、所在部门、职位和入职年份。 使用SORT过程对该数据集进行排序时,在BY语句中先后指定排序 的变量Dept和Entry_Date。 proc sort data=saslib.employee out=saslib.employee_sorted; by Dept Entry_Date; run; 排序后生成的数据集saslib.employee_sorted在VIEWTABLE窗口中打 开如图3.14所示。 图3.13 例3.13输入数据集中部分内容 图3.14 例3.13输出数据集部分内容 默认情况下,SAS根据BY变量的值升序排列分组。 2.使用选项DESCENDING对观测按变量降序排序 在BY语句中,还可以在每个变量之前指定选项DESCENDING对变 量进行降序排序,或者根据需要对部分变量进行升序排序、部分变量降 序排序。其基本形式如下: BY <DESCENDING > 变量1 << DESCENDING > 变量2...>; 如果变量前面存在选项DESCENDING,则该变量在组内按降序排 序,否则按默认的升序排序。 例3.14:首先将saslib.employee中的员工数据按部门名称进行排序 (升序),每个部门内部的入职日期由近到远进行排序(降序)。代码 如下: proc sort data=saslib.employee out=saslib.employee_descending; by Dept descending Entry_Date; run; 所生成的saslib.employee_descending在VIEWTABLE窗口打开如图 3.15所示。 3.找到分组中的第一个和最后一个观测 在使用BY语句时,SAS会自动为BY语句中指定的每个变量生成两 个临时变量:FIRST.BY变量和LAST.BY变量。当变量值在每个分组中 第一次出现时,FIRST.BY变量为1,否则为0;当变量值在每个分组中 最后一次出现时,LAST.BY变量为1,否则为0。通过这两个变量可以找 到分组中的第一个和最后一个观测,并进行相应的处理。在DATA步中 使用SET语句和BY语句的基本形式如下: DATA 数据集; SET 数据集; BY 变量列表; ...其他语句; RUN; 例3.15:取排序生成的saslib.employee_sorted中每个部门最先入职和 最后入职的员工,生成新数据集saslib.employee_fl。 代码如下: data work.employee_fl; set saslib.employee_sorted; by Dept; if first.Dept or last.dept; run; proc print data=work.employee_fl noobs; run; PRINT过程打印的数据集中的数据如图3.16所示。 图3.15 例3.14输出数据集部分内容 图3.16 例3.15打印的输出数据集内容 4.使用选项NODUPKEY删除重复BY变量的观测 使用SORT过程的NODUPKEY可以在对数据集按BY变量进行排序 的同时,删除数据集中BY变量值相同的观测。 例3.16:从原始数据文件contact2.csv读取数据到SAS数据集 saslib.contact2_raw,并对该数据集进行排序。排序输出数据集 saslib.contact2中不包括BY变量值相同的观测,删除的观测保存在数据 集work.contact2_dup中。 数据文件contact2.csv中包含的数据记录如下: Name,Age,Position,Marriage,Address Greg William,42,Manager,Single,"14 Bridge St. San Francisco, CA" Emily Cooker,33,Sales,,42 Rue Marston Henry Cooper,,Office,Married,52 Rue Marston Paris Greg William,,Manager,Single Jimmy Cruze,34,Manager,Single Adam Smith,45,Sales,,"111 Clancey Court, NC" 读取数据文件contact2.csv中的记录创建数据集,并对其进行排序。 要求排序输出的数据集中不包含Name值相同的观测,而且被删除的观 测保存在另外的数据集中。 proc import out=saslib.contact2_raw datafile="c:\sas\data\ch3\contact2.csv" dbms=csv replace; getnames=yes; datarow=2; run; proc print data=saslib.contact2_raw noobs; title "All Observations"; run; proc sort data=saslib.contact2_raw out=saslib.contact2 dupout=work.contact2_dup nodupkey; by Name; run; proc print data=saslib.contact2 noobs; title "Observations with Duplicate BY Values Deleted"; run; proc print data=work.contact2_dup noobs; title "Duplicate Observations"; run; PRINT过程打印的数据集内容如图3.17~图3.19所示。 图3.17 例3.16打印的所有观测数据集内容 图3.18 图3.19 例3.16打印的不含重复BY值观测的数据集内容 例3.16打印的删除的重复BY值观测的数据集内容 在SORT过程中,输入数据集saslib.contact2_raw中的第4条观测(因 为其名字为“Greg William”,与第1条观测重复)未写入数据集 saslib.contact2,而是写入work.contact2_dup中。 创建新变量 3.3 在对SAS数据集进行处理时,经常需要根据原有变量或变量值生成 新变量。根据要实现功能的不同,SAS提供了多种方法,例如通过数据 集选项RENAME=(RENAME语句)、赋值语句、求和语句等来实现不 同的功能。 3.3.1 数据集选项RENAME=和RENAME语句 在DATA步中,可使用数据集选项RENAME=或RENAME语句修改 一个或多个变量的名称。跟前面介绍过的数据集选项KEEP=和DROP= 一样,数据集选项RENAME=也可用于DATA语句中的输出数据集和 SET语句中输入数据集。其基本形式如下: RENAME=(旧变量名-1=新变量名-1 <...旧变量名-n=新变量名-n >) RENAME语句的基本形式如下: RENAME 旧变量名-1=新变量名-1 <...旧变量名-n=新变量名-n >; 其中: ·旧变量名指定在输入数据集中或当前DATA步中新创建的变量。 ·新变量名指定在输出数据集中使用的变量名或变量名称列表。新 的变量名仅被写入输出数据集。 ·数据集选项RENAME=或RENAME语句可以修改多个变量的名 称。 例3.17:将数据集saslib.contact2中的变量Name重命名为 Full_Name,并将原有Name中的姓和名分开为Last_Name和 First_Name。 下面3段代码分别在SET语句中使用数据集选项RENAME=、在 DATA语句中使用数据集选项RENAME=和使用RENAME语句来实现。 代码1: data work.contact2_rn; set saslib.contact2 (rename=(Name=Full_Name)); First_Name=scan(Full_Name,1); Last_Name=scan(Full_Name,2); run; 代码2: data work.contact2_rn (rename=(Name=Full_Name)); set saslib.contact2; First_Name=scan(Name,1); Last_Name=scan(Name,2); run; 代码3: data work.contact2_rn ; set saslib.contact2; rename Name=Full_Name; First_Name=scan(Name,1); Last_Name=scan(Name,2); run; 这3段代码中都使用了SCAN函数,将原Name变量中用空格隔开的 姓和名提取到变量Last_Name和First_Name中。注意,在引用原Name变 量进行操作时,代码1使用了新变量名(Full_Name)、代码2和代码3使 用了旧变量名(Name)。这是因为在SET语句中使用选项RENAME= 时,SAS为输入数据集所创建的PDV中的变量名就成为了新变量名,所 以在编程语句中引用原变量时必须使用新的变量名。但是如果在DATA 语句中使用选项RENAME=或RENAME语句,新变量名仅会写入输出数 据集,所以在DATA步的其他语句中引用该变量时,必须使用旧的变量 名。 PRINT过程打印3段代码的输出数据集work.contact2_rn的内容相 同,如图3.20所示。 图3.20 例3.17打印输出数据集内容 在SET语句中使用数据集选项RENAME=、在DATA语句中使用数 据集选项RENAME=和使用RENAME语句的比较如下: ·RENAME语句不能用于PROC步,但是数据集选项RENAME=可 以。 ·数据集选项RENAME=可以对每个输出数据集的变量单独更改名 称,而RENAME语句修改的变量名称对所有输出数据集都起作用。 ·SET语句中的数据集选项RENAME=会修改变量名,此时,在编程 语句中引用原变量时必须使用新的变量名;如果在输出数据集中使用 RENAME=选项或使用RENAME语句,在编程语句引用原变量时必须使 用旧的变量名。 3.3.2 赋值语句创建新变量 在DATA步中出现的变量,如果不属于输入数据集的变量,而且也 不是自动变量,SAS则会为其创建新变量,所创建的新变量默认会写入 输出数据集。可以通过前面介绍的数据集选项DROP=、KEEP=、DROP 语句、KEEP语句保留或删除不需要的中间变量。 赋值语句是常见的创建新变量的方法,通常将新变量放在赋值语句 的等号(=)左侧来创建新变量,并同时给该变量赋值。在前面的例子 已经看到了使用赋值语句修改已经存在的变量值(例3.8~例3.12)和创 建新变量(例3.17)。 例3.18:数据集saslib.revenue_quarter中存储了公司产品今年不同季 度的销售额,年底要计算全年的总销售额。 该数据集在VIEWTABLE窗口中打开如图3.21所示。 图3.21 例3.18输入数据集部分内容 下面对各季度的销售额(Rev_Q1、Rev_Q2、Rev_Q3和Rev_Q4) 相加得到总销售额Total_Rev,最后使用FORMAT语句修改其输出格 式。 data work.revenue; set saslib.revenue_quarter; Total_Rev = Rev_Q1 + Rev_Q2 + Rev_Q3 + Rev_Q4; format Total_Rev DOLLAR10.; run; 所生成的数据集work.revenue在VIEWTABLE窗口中打开如图3.22所 示。该数据集包含了表示全年总销售额的变量Total_Revenue。 图3.22 3.3.3 例3.18输出数据集部分内容 对多个观测求和 对数据集进行处理时,经常会需要获得整个数据集中的所有观测或 特定一部分观测中的变量值的总和,例如,公司所有产品的总销售额、 产品在各地区的总销售额等。在DATA步中,可使用求和语句、 RETAIN语句、SUM函数等方式对多个观测中的变量值进行求和。 1.求数据集中变量的总和 求和语句的基本形式如下: 变量+表达式; 其中: ·变量指定累加变量的名称,该变量包含一个数字值。 ·表达式是任意的SAS表达式。当迭代中表达式的值为缺失值时, SAS会将表达式的求值结果当作0处理。 SAS在读取第一个观测前将求和语句中的累加变量的初始值设置为 0。如果要将求和变量的初始值设置为其他的值,可使用RETAIN语句 (后面介绍)。 在每次迭代中,SAS执行该语句时将表达式的值与该变量的值相 加,结果保持在该累加变量中,下次迭代时仍然可以使用。在一次迭代 中,当表达式不为缺失值时,求和语句等同于赋值语句“变量=变量+表 达式;”。在求和语句中,当表达式为缺失值时,SAS将表达式的值当 作0处理,而赋值语句不会这样。 例3.19:读入公司销售数据建立数据集,并计算总销售额。 公司所有员工销售数据所在外部数据文件sales.dat的内容如下: ET001,Kevin Lee,TSG,$10000 ED002,Faith May,CSG,$12000 ET004,Jackson Cook,TSG,$18000 EC002,Hailey Leonard,CSG,$23000 ED004,Jack Smith,QSG,$5000 为了让所生成的数据集中的变量Sales和Total_Sales的输出格式同输 入格式一样,在DATA步使用FORMAT语句指定变量Sales和Toal_Sales 的格式为DOLLAR10.。关于FORMAT语句本书第5章会介绍。 filename exfiles "c:\sas\data"; data saslib.sales; length Name $20; infile exfiles(sales) dsd; input Emp_ID $ Name $ Dept $ Sales:COMMA10.; format Sales DOLLAR10.; run; data work.sales_sum; set saslib.sales; Total_Sales+Sales; format Total_Sales DOLLAR10.; run; proc print data=work.sales_sum noobs; run; PRINT语句打印所生成的数据集数据如图3.23所示。其中,变量 Toal_Sales为前面所有观测中变量Sales值之和。 图3.23 例3.19打印的输出数据集内容 例3.20:计算公司所有销售人员的总销售额。与上例不同的是,销 售数据中存在缺失值。 包含缺失值的数据集的内容如图3.24所示。 使用求和语句对Sales变量进行求和的代码如下: data work.sales_sum; set saslib.sales2; Total_Sales+Sales; format Total_Sales DOLLAR10.; run; 对Sales求和后得到的数据集如图3.25所示。因为第三条观测中的 Sales为缺失值,求和语句将其当作0处理,所以第三条观测的 Total_Sales值与第二条相同。 图3.24 例3.20输入数据集内容 图3.25 例3.20打印的输出数据集内容 2.求每个BY组的总和 前面提到过,SAS会为BY语句中指定的每个变量生成临时变量 FIRST.变量和LAST.变量。在分组数据中,可以使用这两组临时变量计 算每个分组中变量值的总和。 例3.21:数据集sashelp.shoes中的观测已经按照地区(Region)、附 属品牌(Subsidiary)进行了排序。现在分别计算各地区各附属品牌的 销售额总和。 代码如下: data work.shoes_subsidiary (drop=Sales); set sashelp.shoes (keep=Region Subsidiary Sales); by Region Subsidiary; if First.Subsidiary then Total_Sales_Subsidiary=0; Total_Sales_Subsidiary+Sales; if Last.Subsidiary; format Total_Sales_Subsidiary DOLLAR10.; run; 代码说明如下: ·在SET语句中,数据集选项KEEP=指定仅读取输入数据集中的变量 Region、Subsidiary和Sales。 ·在BY语句中,指定BY变量Region和Subsidiary。要求SET语句中的 数据集已经按照Region和Subsidiary排序。SAS自动生成临时变量 First.Region、Last.Region、First.Subsidiary和Last.Subsidiary。其中 First.Region、Last.Region在本例中未用到。 ·因为需要计算各附属品牌的总销售额,所以每次出现新品牌 (First.Subsidiary为1)时将累加变量,并将Total_Sales_Subsidiary置为 0。 ·求和语句将对Sales值进行累加,结果存储在Total_Sales_Subsidiary 中。 ·当Subsidary的值为该分组内最后一个值(Last.Subsidiary为1)时, 才将当前输出PDV中的观测输出,此时Total_Sales_Subsidiary为该品牌 的总销售额。当前输出PDV中的变量为Region、Subsidiary、Sales和 Total_Sales_Subsidiary。 ·由于输出观测中的变量Sales为每个分组内最后一个产品的销售 额,没有意义,因此在DATA语句中使用数据集选项DROP=指明不将变 量Sales写入输出数据集work.shoes_subsidiary。 ·此外,FORMAT语句在编译时指定累加变量Total_Sales_Subsidiary 的输出格式为DOLLAR10.。 所生成的work.shoes_subsidiary在VIEWTABLE窗口中打开如图3.26 所示。 图3.26 例3.21输出数据集部分内容 3.使用RETAIN语句保持变量值 默认情况下,DATA步中所有变量在每次迭代开始前都会被设置为 缺失值。而RETAIN语句中指定的变量则不会,其值会一直保持着,在 下次迭代中仍然可以使用。RETAIN语句的基本形式如下: RETAIN 元素列表1 <初始值1 | 初始值列表1> <...元素列表n <初始n | 初始值列表n> >; 其中: ·元素列表指定要在历次迭代中保持其值的变量名、变量列表或数 组名。 ·初始值或初始值列表为其前面的元素指定的初值(为数字或字 符)。当指定一个初始值时,该初始值被指定为其前面元素列表中的所 有元素的初值。若指定的是初始值列表(多个初始值),SAS会将该列 表中的第一个值指定给第一个元素,第二个值指定给第二个元素,依此 类推。当未指定初始值或初始值列表时,其前面的元素的初始值为缺失 值。 例3.22:基于数据集saslib.sales,使用RETAIN语句和赋值语句计算 公司产品全年总销售额。 示例代码如下: data work.sales_retain; set saslib.sales; retain Total_Sales 0; Total_Sales=Total_Sales+Sales; format Total_Sales DOLLAR10.; run; proc print data=work.sales_retain noobs; run; RETAIN语句将Total_Sales的初值设置为0,并告诉SAS在每次迭代 中保持Total_Sales的值。接下来的赋值语句将上次迭代中计算得到的 Total_Sales值与当前观测中Sales的值相加,结果存储在Total_Sales中。 PRINT语句打印输出数据集的内容如图3.27所示。 图3.27 例3.22打印的输出数据集内容 该例使用的数据saslib.sales中变量Sales没有缺失值。如果Sales中存 在缺失值,那么赋值语句中表达式(Total_Sales+Sales)的结果也为缺 失值,这样会导致当前及其后所有迭代中Total_Sales的值都为缺失值。 读者可在SET语句中使用例3.20中使用的带缺失值的数据集 saslib.sales2,并查看其生成数据集的内容。当数据值中包含缺失值时, 还可以使用SUM函数对变量求和,该函数只会计算非缺失值的和,具体 的接下来会介绍。 此外,使用RETAIN语句还需要注意的是,如果该变量名仅在 RETAIN语句中出现,并且RETAIN语句中未对其赋初值,则该变量不 会写入输出数据集中。反之,如果RETAIN语句中给出了变量初始值, 即使该变量仅在RETAIN语句中出现,该变量也会写入输出数据集。 4.使用SUM函数 SUM函数返回非缺失值参数的和。SUM函数的基本形式如下: SUM(参数, 参数...); 该求和语句等效于RETAIN语句和SUM函数的组合。其中,参数指 定为数字常量、数字变量或数字表达式。如果参数中包含缺失值,且所 有参数都是缺失值,则返回缺失值;若任一参数不是缺失值,则返回非 缺失值参数的总和。与求和语句不同,SUM函数不会保持任何变量的 值,所以如果要想保持的变量,应该使用RETAIN语句。 例3.23:基于数据集saslib.sales,计算公司产品全年总销售额。 示例代码如下: data work.sales_sumfunc; set saslib.sales; retain Total_Sales 0; Total_Sales=sum(Total_Sales,Sales); format Total_Sales DOLLAR10.; run; 所生成的数据集work.sales_sumfunc的数据和例3.22一样。 循环和数组 3.4 SAS还提供了循环语句以满足在编程中需要多次执行相同操作的情 况。有时还需要对不同的变量执行相同的操作,此时可定义SAS数组, 并通过数组名和下标来引用这些变量。 3.4.1 循环 SAS循环语句通常有如下几种形式:迭代DO语句、DO WHILE语句 和DO UNTIL语句。 1.迭代DO语句 迭代DO语句的基本形式如下: DO 索引变量=开始值 <TO 结束值> <BY递进值> <WHILE(表达式)> <UNTIL(表达式)>; … SAS语句… END; 其中: ·索引变量用于指定一个变量,若该变量不存在,则创建新变量。 DO语句和END语句之间的语句称为DO组,索引变量的值会控制DO组 的执行。 ·开始值指定索引变量的初始值,可以是表达式或表达式序列。DO 组的执行从“索引变量=开始值”开始。在循环的第一个迭代开始前,对 开始值求值。如果结束值和递进值不存在,那么开始值可能是一系列 项,则DO语句的形式如下。 DO 索引变量=项1 <, …项n>; 项1~项n可以是数字常量、字符常量或变量。SAS为列表中的每个 项执行一次DO组。 ·结束值指定索引变量的结束值。当开始值和结束值都存在时,DO 组执行直到下面任意一种情况发生时循环执行结束:索引变量的值超过 结束值;DO组中存在指示退出循环的语句,例如LEAVE语句、GO TO 语句;如果有WHILE或UNTIL选项,则WHILE之后的表达式不满足或 UNTIL之后的表达式满足(可参考后面对DO UNTIL语句和DO WHILE 语句的介绍)。 ·递进值指定一个数字,或者是产生数字值的表达式,来控制索引 变量的增量。递进值在循环执行前进行计算。因此,在DO组内对递进 值的修改不会影响循环迭代次数。每次迭代后,索引变量的值为其当前 值的基础上增加递进值。如果未指定递进值,则索引变量的值增加1。 例3.24:使用DO循环生成数据集,数据集中包含两个变量x和y。x 的值为1、3、5,y为x的平方。 代码如下: data work.square; do x=1 to 5 by 2; y=x**2; output; end; run; proc print data=work.squart noobs; run; PRINT过程打印生成的数据集的内容如图3.28所示。 图3.28 例3.24打印数据集内容 2.DO UNTIL语句 DO UNTIL语句重复执行DO循环中的语句,直到条件为真。DO UNTIL语句的基本形式如下: DO UNTIL (表达式); ...SAS语句... END; 表达式可是任意的SAS表达式。DO UNTIL语句中至少包含一个表 达式,也可以包含多个表达式。在DO循环中的语句执行完成后将对表 达式求值。所以,DO循环至少被执行一次。 例3.25:将给定字符串中包含的各个单词分开写入数据集中。 在DO循环中使用SCAN函数依次读取字符串中的单词,并存储在变 量word中且输出。LENGTHN函数用于判断循环结束的标志。当SCAN 函数扫描到字符串结尾,word值为空时,LENGTHN函数返回0,循环 结束。代码如下: data work.all; length word $20; drop string; string='The quick brown fox jumps over the lazy dog.'; do until(lengthn(word)=0); count+1; word=scan(string, count); output; end; run; proc print data=work.all noobs; run; PRINT过程打印字符串中各个单词的内容如图3.29所示。 图3.29 例3.25打印数据集内容 3.DO WHILE语句 DO WHILE语句在条件为真时重复执行DO循环。DO WHILE语句 的基本形式如下: DO WHILE (表达式); ...SAS语句... END; 表达式可是任意的SAS表达式。DO UNTIL语句中至少包含一个表 达式,也可以包含多个表达式。在DO循环中的语句被执行前先对表达 式求值。如果第一次执行时表达式为假(false),DO循环不会被执 行。 例3.26:将给定字符串中包含的各个单词分开写入数据集中。 代码如下: data work.all; length word $20; drop string; string=' '; word=scan(string, 1); do while(lengthn(word)>0); count+1; word=scan(string, count); output; end; run; 因为要WHILE表达式必须成立才会执行DO循环,也就是说在执行 到DO WHILE语句之前,word不能为空或缺失值,所以代码中先对word 值赋值(word可以赋值为其他任何不为空的字符值)。DO循环中,当 SCAN函数扫描到字符串结尾,word值为空时,LENGTHN函数返回0, 不满足条件lengthn(word)>0,循环结束。所生成的数据集与例3.25的 结果一样,这里不再给出。 下面是对3种循环语句的比较: ·迭代DO语句基于索引变量值重复执行DO语句和END语句之间的 SAS语句。使用迭代DO语句较容易控制循环次数。 ·DO UNTIL语句重复执行在DO循环中的语句,直到条件为真。DO UNTIL语句在每次DO循环迭代结束后检查条件。 ·DO WHILE语句在条件为真时重复执行DO循环中的语句。DO WHILE语句在每次DO循环迭代开始前检查条件。 3.4.2 SAS数组 如果需要对许多变量做相同的操作,虽然可以通过写一系列赋值语 句来实现,但是使用数组会简化程序代码。 数组是一组以特殊顺序排列并由数组名标识的SAS变量。只要一组 变量名都是同一类型,例如都是数值型或字符型,就可以为该组变量定 义一个数组。这些变量可以是数据集中已经存在的,也可以是要创建的 新变量。数组仅仅在当前DATA步中存在,在同一DATA步中,数组按 名字区分。SAS数组不是一种数据结构,只是临时标识一组变量较方便 的方法,在这点上SAS数组不同于其他编程语言中的数组。 1.数组定义及引用 在DATA步中使用ARRAY语句定义数组。定义数组的基本形式如 下: ARRAY 数组名 {下标} <$> <长度> <数组元素> <(初始值列表)>; 其中: ·数组名是指定给数组的名称。该数组名在同一DATA步中不能与任 何其他变量名或关键字重名。其命名需遵循SAS变量的命名规范(不超 过32个字符,以字母或下划线开始,可包含字母、数字和下划线)。 ·下标可以有多种形式,通常为指定数组元素个数、上下边界或*。 *表示通过计算数组元素个数确定数组下标。 ·$指定数组中的元素是字符元素。如果数组元素是数字元素或先前 已经定义的字符元素,则不需要使用$。 ·长度指定先前未指定长度的元素的长度。 ·数组元素指定组成数组的元素名称。 ·初始值列表给出数组中对应元素的初始值,以空格隔开。 定义数组时的括号可以是()、{}或[]。定义数组时的下标形式指 定了数组维度、各个维度的下边界和上边界。还可以通过不同格式的下 标指定多维数组,多个维度之间用逗号(,)分隔,同一个维度内的上 下边界使用冒号(:)分隔,如表3.8所示。 表3.8 数组的维度、下边界、上边界示例 数组元素可以是数值型或字符型,并且可以以任何顺序列出,其个 数必须等于括号{}中给出的下标值。 数组元素可以是已经存在的变量或不存在的变量,当数组元素是不 存在的变量时,SAS会创建新变量。除了列出变量外,变量可以是关键 字_NUMERIC_、_CHARACTER_或_ALL_,其说明如表3.9所示。 表3.9 数组定义中使用_NUMERIC_、_CHARACTER_或_ALL_ 还可使用关键字_TEMPORARY_来创建临时数据元素。临时数据元 素不会出现在输出数据集中,并且其值总是自动保持,而不会在DATA 步的每次迭代开始时自动设置为缺失值。临时数组元素仅用于计算,如 果要保留计算结果,需要将结果赋值给其他变量。使用临时数组元素的 好处是可以提高性能。 使用数组名引用变量时,要给出数组名和该变量的下标。例如,使 用如下ARRAY语句定义变量STORE,共4个元素,分别为变量Macys、 Penneys、Sears和Target。 array Store {4} Macys Penneys Sears Target; 使用数组名引用变量时,STORE{1}是变量Macys,STORE{2}是 Penneys,STORE{3}是Sears,STORE{4}是Target。 如果定义数组时指定了上下边界,例如: array Month {4:6} April May June; 则Month{4}是变量April,Month{5}是变量May,Month{6}是变量 June。 这样就可以在循环中通过改变数组下标来操作每个数组元素对应的 变量,所以数组经常在DO组中使用,后面会介绍。 2.数组函数 SAS提供了DIM、HBOUND和LBOUND函数返回数组中指定维度 的元素个数、上边界和下边界。 DIM函数返回一维数组中的元素个数或多维数组中指定维度中的元 素个数。其形式如下: DIM<n>(数组名) 或 DIM(数组名, 维度) HBOUND函数返回一维数组的上边界或多维数组中指定维度的上 边界。其形式如下: HBOUND<n>(数组名) 或 HBOUND(数组名, 维度) LBOUND函数返回一维数组的下边界或多维数组中指定维度的下边 界。其形式如下: LBOUND<n>(数组名) 或 LBOUND(数组名, 维度) 其中: ·n指定维度的整型常量。如果未指定n值,则返回数组的第一维的 结果。 ·数组名指定在同一DATA步中先前定义的数组名称。 ·维度是指定维度的数字常量、数字变量或数字表达式。 ·例如,对于下面的数组定义,各数组函数的返回值如表3.10所示。 表3.10 数组函数及返回值示例 array x {2,11:15} a1-a10; 3.DO循环中引用数组元素 将数组与迭代DO语句结合使用,在对用数组所表示的变量进行处 理时会变得简单。其基本形式如下: DO 索引变量=1 TO 数组元素个数; … SAS语句… END; 例3.27:使用例3.18中的数据集saslib.revenue_quarter制定明年的销 售计划,预期各季度的销售额为今年的150%。 代码如下: data saslib.revenue_2014; set saslib.revenue_quarter; array Quarter {4} Rev_Q1-Rev_Q4; do i=1 to 4; Quarter{i} = Quarter{i}*1.5; end; drop i; run; 其中: ·ARRAY语句定义了数组Quarter,其元素个数为4。因为变量 Rev_Q1到Rev_Q4在输入数据集中存在,所以该数组只是引用存在的变 量Rev_Q1到Rev_Q4,并不创建新变量。这里数组元素使用了简化的表 达方式。 ·在DATA步的每次迭代中,通过在DO循环中引用Quarter{1}到 Quarter{4}来分别操作变量Rev_Q1到Rev_Q4,将销售额在现有销售数据 的基础上增加50%。 数据集saslib.revenue_2014在VIEWTABLE窗口打开如图3.30所示。 图3.30 例3.27输出数据集部分内容 3.5 SAS常用函数 SAS函数是编程语言的一个组件,可接受参数、执行计算或进行其 他操作并返回值。返回值是字符型或数值型的结果,可用于赋值语句或 表达式中。SAS包含很多函数,也可以自定义函数。在BASE SAS软件 中,SAS函数可用于DATA步编程、WHERE表达式、宏语言语句、 PROC REPORT和结构化查询语言SQL(Structured Query Language)。 本节将会介绍一些常用的SAS函数。 CALL例程转变变量值或执行其他系统函数。CALL例程和函数相 似,不同的是CALL例程不能用于赋值语句或表达式。所有的CALL例 程都使用CALL语句调用。本书不介绍CALL例程,有兴趣的读者可参 考SAS帮助文档进行学习。 3.5.1 函数语法 SAS函数的形式如下: 函数名(参数1 <, ...参数n>) 函数名(OF 变量列表) 函数名(参数 | OF 变量列表 | OF数组名{*} <..., 参数 | OF 变量列表 | OF数组名{*} >) 其中: ·函数名用于给出函数名称。 ·参数可以是变量名、常量或任何SAS表达式。多个参数间使用逗号 (,)分隔。 ·变量列表可以是任何形式的变量列表。多个列表之间使用空格分 隔。例如sum(of x y z)、sum(of x1-x10)、sum(x,of x1-x5y1y5)、sum(x,of x1-x5,of y1-y5)。最后两种表示方式具有同等效 果。 ·数组名{*}指在当前DATA步中已经定义的数组。 3.5.2 数值函数 表3.11给出了SAS常用的数值操作的函数。 表3.11 常用数值操作函数 数值函数的使用相对简单,例3.23中给出了使用SUM函数的例子, 这里不再举例说明。 3.5.3 字符操作函数 常用的字符操作函数如表3.12所示。 表3.12 常用字符操作函数 例3.28:将数据集saslib.contact2中的变量Name中的姓和名分开为 Last_Name和First_Name。 在例3.17中已经使用了SCAN函数将Full_Name中由空格分隔的第1 个单词和第2个单词存储在了First_Name和Last_Name中,本例则使用 INDEX和SUBSTR函数来完成该部分功能。例3.17中将原变量Name重命 名为Full_Name这部分本例不涉及。代码如下: data work.contact2; set saslib.contact2; split=index(Name, ' '); First_Name=substr(Name,1,split-1); Last_Name=substr(Name,split+1); drop split; run; proc print data=work.contact2 noobs; run; PRINT过程打印输出数据集的数据如图3.31所示。 图3.31 例3.28输出数据集内容 这里以DATA步中第一次迭代的变量值(Name的值为“Greg William”)为例来解释说明。 ·第一条赋值语句:INDEX函数返回该字符串中空格的位置5,并存 储在split中。 ·第二条赋值语句:右侧SUBSTR函数返回字符串“Greg William”中 从第1个字符开始到第4(5-1)个字符中的字符串“Greg”,并存储在 First_Name中。 ·第三条赋值语句:右侧SUBSTR函数返回字符串“Greg William”中 从第6(5+1)个字符开始剩下的所有字符“William”,并存储在 Last_Name中。 注意 这里为了便于理解,在给出变量的值时未将字符串中的尾 缀空格列出。例如,因为Full_Name的长度为20个字符,所以所存储的 字符串实际上是“Greg William”(后面共8个空格)。字符值中包含的空 格对理解字符串操作非常重要。 例3.29:数据集saslib.shop包含各汽车经销商的4S店信息,其中包 括变量Street、City、State。现在要将这3个变量组合成完整的地址信 息。 数据集saslib.shop的内容如图3.32所示。 图3.32 例3.29输入数据集内容 可使用级联操作符||组合完整地址,应在Street、City、State之间使 用标点符号,因为多个空格字符在HTML中会显示为一个空格。这里为 了清楚,将PRINT过程的打印结果输出到PDF文件中。输出PDF文件使 用了SAS的输出交付系统ODS(Output Delivery System),在本书第5章 会进行介绍。代码如下: data work.shop_fulladdr; set saslib.shop (drop=telephone zip); Full_Address=Street || ", " || City || ", " || State; drop Street City State; run; ods pdf file="c:\sas\data\output\full_address.pdf"; proc print data=work.shop_fulladdr noobs; run; ods pdf close; PDF文件的内容如图3.33所示。其中Full_Address列的地址中包含很 多空格。这是因为原数据集中的变量Street和City数据值的长度小于变量 的长度,这时SAS会在数据值后补充空格以达到给定变量长度。以数据 集saslib.shop中的第一个观测为例。变量City的长度定义为20,第一个观 测中City的值实际为“Culver City”。对Street也是相同的情况。因为State 的长度是2,所以其后不会存在空格。 图3.33 例3.29生成PDF文件内容 例3.30:使用TRIM函数将各字符变量尾缀空格删除,并将其进行 级联产生完整的地址信息。 因为这里的State长度为2,也可以不对State变量使用TRIM函数。代 码如下: data work.shop_fulladdr; set saslib.shop (drop=telephone zip); Full_Address=trim(Street) || ", " || trim(City) || ", " || trim(State); drop Street City State; run; ods pdf file="c:\full_address.pdf"; proc print data=work.shop_fulladdr noobs; run; ods pdf close; 所生成的PDF文件的内容如图3.34所示,变量Full_Address列中没有 多余的空格。 图3.34 删除了空格的数据 例3.31:直接使用CATX函数删除各字符变量值的尾缀空格,并将 其进行级联产生完整的地址信息。 CATX函数也可以删除字符串的前导空格,虽然本例中字符串不含 前导空格。代码如下: data work.shop_fulladdr; set saslib.shop (drop=telephone zip); Full_Address2=CATX(", ", Street, City, State); drop Street City State; run; ods pdf file="c:\full_address.pdf"; proc print data=work.shop_fulladdr noobs; run; ods pdf close; 所产生的数据集与上例相同,这里不再给出。 3.5.4 数值与字符转换函数 在介绍表达式的时候介绍过,当代码中给出的值与所需要的类型不 匹配时,例如,在需要数字值的地方使用了字符变量,SAS会试图自动 将该值转换成所期望的类型,但是,自动转换有时候会出错或产生意外 的结果。SAS提供了PUT和INPUT函数进行显式类型转换。这两个函数 很有用,即使有些情况自动转换能够处理,使用显式类型转换也会更有 效率。 1.PUT函数 PUT函数使用指定的格式返回值,可用于将数字值转换成字符值。 其基本形式如下: PUT(源, 格式) 其中,源为要进行重新格式化的常量、变量或表达式,可以是字符 型或数值型。格式为要应用在源上的SAS格式。PUT函数可用于将数字 根据格式转换为字符或将字符值转换为其他字符。默认情况下,如果源 是数值型,结果字符串会向右对齐,如果源是字符型,则结果字符串会 向左对齐。也可以在格式中添加对齐标识-L、-C、-R分别表示左对齐、 居中或右对齐,改变默认对齐方式。格式必须与源的类型一致。也就是 说,如果源是字符,格式名必须以$符号开始;如果源是数字,格式则 不能以$开始。PUT函数不影响数据集中的变量格式或属性。 例3.32:在对总公司的多个子公司员工信息进行合并时,发现某个 子公司员工数据中的员工ID为数字值,而其他子公司员工数据中的员工 ID为字符值,这时需要将该子公司员工ID的数字转换成字符型,以便进 行合并操作。 该子公司员工信息保存在saslib.employee2中,使用PUT函数对 Emp_ID的数值进行转换,并创建新的字符型变量New_Emp_ID,同 时,使用DROP语句将原始变量Emp_ID删除,之后再使用RENAME语 句将New_Emp_ID改名为Emp_ID。这样,所生成数据集中包含的变量 名就会保持不变。该过程很容易理解,这里不再给出数据示例进行讲 解。相关代码如下: data saslib.employee2; set saslib.employee2; New_Emp_ID=put(Emp_ID, best10.); drop Emp_ID; rename New_Emp_ID=Emp_ID; run; 2.INPUT函数 INPUT函数返回当SAS使用指定输入格式转换SAS值之后的结果。 其基本形式如下: INPUT(源, 输入格式) 其中,源为要应用输入格式的字符常量、字符变量或字符表达式。 格式为要应用在源上的SAS输入格式。 INPUT函数会将源的值使用指定的输入格式进行转换。INPUT函数 可用于将字符值转换为数字值或其他字符值。输入格式指定了结果是数 值型还是字符型。INPUT函数也不影响数据集中的变量输入格式或属 性。 例3.33:数据集saslib.sales中的日期值(Date)以字符方式进行存 储,其数据内容和变量的属性如图3.35和图3.36所示。 图3.35 例3.33中输入数据集数据值 图3.36 例3.33中输入数据集变量描述 公司需要对员工入职日期进行排序,首先要将日期值(Date)转换 为数字。这里使用INPUT函数,并使用输入格式date9.将字符格式的日 期值(例如“01JAN2012”)转换为该日期对应的数字进行存储。代码如 下: data saslib.sales; set saslib.sales; Num_Date=input(Date, date9.); drop Date; rename Num_Date=Date; run; proc print data=saslib.sales noobs; run; proc contents data=saslib.sales; run; PRINT过程和CONTENTS过程打印的数据集内容和属性如图3.37和 图3.38所示。而且Date变量的类型为数值型,这样就可以根据Date值对 整个数据集进行排序了。 图3.37 图3.38 3.5.5 例3.33输出数据集数据值 例3.33输出数据集变量描述 与日期时间相关的函数 SAS提供日期(date)、时间(time)和日期时间(datetime)函数 从日期、时间和日期时间值中得到年份、月份、日、小时、分钟、秒等 信息,它们也可以将这些信息组成SAS的日期、时间和日期时间值。表 3.13给出了SAS中常用的与日期、时间相关的函数。除此之外,SAS还 提供对日期的间隔进行操作的函数,详细情况请参考SAS帮助文档。 表3.13 常用日期时间相关函数 例3.34:将公司员工入职日期中的年份、月份和日分别提取出来, 建立新的变量Year、Month和Day。数据集saslib.employee的内容参考例 3.13。 代码如下: data work.employee_ymd; set saslib.employee; Year = year(Entry_Date); Month = month(Entry_Date); Day = day(Entry_Date); run; 上面分别使用YEAR函数、MONTH函数和DAY函数得到原数据集 Entry_Date中员工入职年份、月份和日期,所生成的数据集的内容如图 3.39所示。 图3.39 例3.34输出数据集部分数据值 3.6 将数据集写出到外部文件 除了可以在SAS环境中对SAS数据集进行加工处理外,SAS数据集 的数据还可以导出到外部文件中,以便在未安装SAS软件的环境中使 用,SAS提供了EXPORT过程来实现此功能。EXPORT过程可以将SAS 数据集按照指定格式写入外部文本文件。当SAS安装中存在 SAS/ACCESS to PC Files许可时,EXPORT过程还可以读取写入PC文件 中的外部数据,包括Microsoft Access数据库文件、Miscrosft Excel工作 簿、Lotus 1-2-3文件、dBase文件、JMP文件、SPSS文件、Stata文件和 Paradox等。 本节介绍如何使用EXPORT过程将SAS数据集导入常用的外部文 件,例如带分隔符的文本文件和Microsoft Excel工作簿。同时,SAS还 提供了EXPORT向导(选择菜单“文件” “导出数据”)通过图形界面的 方式将SAS数据集中的数据导出到上述类型的文件中。导出过程所生成 的SAS语句也可以保存起来供以后使用。 EXPORT过程的基本形式如下: PROC EXPORT DATA=数据集 OUTFILE=文件名 | 文件引用 | OUTTABLE=表名 DBMS=数据源标识符 ; RUN; 其中: ·DATA=指定要导出的SAS数据集。其后可添加数据集选项。 ·OUTFILE=指定要将SAS数据集中的数据写入的文件,可以是物理 路径或文件引用;OUTTABLE=指定将SAS数据集的内容导入Microsoft Access数据库时的表名。 ·DBMS指定所导出的数据类型。SAS支持多种数据类型,例如 DLM、CSV、TAB、ACCESS、XLSX、XLS、EXCEL、JMP、DTA、 SPASS等。 其中,DLM、CSV、TAB表示导出的数据文件是分别由指定字符 (默认为空格)、逗号和制表符分隔的文本文件;ACCESS表示为使用 LIBNAME语句的Miscrosoft Access表;XLSX表示导出的数据文件为 Micorsoft Excel 2007或2010的工作簿。可参考SAS帮助文档关于导入数 据类型和各类型的详细信息。 在EXPORT过程中还可以指定参数REPLACE,表示当该OUTFILE= 指定的文件存在时覆盖该文件,如果不指定REPLACE,则EXPORT过 程不会覆盖已经存在的文件。 1.导出到带分隔符的文件 SAS使用EXPORT过程将数据集中的数据导出到带分隔符的文件 (包括DBMS为DLM、CSV和TAB)中时,还可指定表3.14中所示的参 数。 表3.14 EXPORT过程的部分常用参数 例3.35:将公司员工信息导出到文件中,文件各列使用逗号(,) 分隔。 代码如下: proc export data=saslib.employee outfile='C:\SAS\data\employee.dat' dbms=dlm replace; delimiter=','; putnames=no; run; 所导出的文件的部分内容如下: Emp_ID,Emp_Name,Dept,Title,Entry_Date ED003,Benjamin Leslie,DSG,Senior Manager,04/26/1993 EC003,Thomos Oliver,CSG,Senior Analyst,08/01/1998 EC011,Adam Scott,CSG,Analyst,11/20/1998 EC012,Jackson Cook,CSG,Manager,06/07/1998 EQ002,Sara Duncan,QSG,Analyst,03/30/1999 2.导出到CSV文件 例3.36:将数据集saslib.employee中的文件导出到CSV文件。 代码如下: proc export data=saslib.employee outfile="C:\SAS\data\employee.csv" dbms=csv; run; DBMS为CSV会默认使用逗号分隔所生成文件中的各个列。该代码 所生成的文件内容与上例相同,这里不再给出。 还可以将上面代码中的DBMS=指定为“TAB”,这时会生成各个列 之间由制表符分隔的文件。 3.使用数据集选项导出部分数据 在PROC EXPORT中还可以使用数据集选项导出部分变量或部分观 测。 例3.37:仅将所有员工的姓名、所在部门和入职日期导出到CSV文 件。因为数据集saslib.Employee中还包含其他变量,所以使用数据集选 项KEEP=指定要导出的变量。代码如下: proc export data=saslib.employee (keep=Name Dept Entry_Date) outfile='C:\SAS\data\employee.csv' dbms=csv replace; run; 导出的文件部分内容如下: Emp_ID,Emp_Name,Entry_Date ED003,Benjamin Leslie,04/26/1993 EC003,Thomos Oliver,08/01/1998 EC011,Adam Scott,11/20/1998 EC012,Jackson Cook,06/07/1998 EQ002,Sara Duncan,03/30/1999 例3.38:将DSG部门的所有员工信息导出到CSV文件中。 代码如下: proc export data=saslib.employee (where=(Dept="DSG")) outfile='c:\sas\data\dsg.csv' dbms=csv replace; run; 使用数据集选项WHERE=指定导出满足条件的观测。所导出的文件 的部分内容如下: Emp_ID,Emp_Name,Dept,Title,Entry_Date ED003,Benjamin Leslie,DSG,Senior Manager,04/26/1993 ED013,Alexandra May,DSG,Manager,04/11/2000 ED004,Christian Peters,DSG,Senior Analyst,05/18/2001 ED011,Hunter Joyce,DSG,Analyst,08/13/2001 ED005,Jasmine Rose,DSG,Senior Analyst,11/15/2003 4.导出到Microsoft Excel文件 例3.39:将数据集saslib.employee中的文件导出到Microsoft Excel文 件。 代码如下: proc export data=saslib.employee outfile="C:\SAS\data\employee.xlsx " dbms=xlsx ; run; 所生成的Microsoft Excel文件打开如图3.40所示。 图3.40 例3.39导出的Microsoft Excel文件部分内容 3.7 本章小结 本章首先介绍了对单个数据集进行操作的语句和选项,包括SET语 句和选取部分变量的选项KEEP=和DROP=以及KEEP语句和DROP语句 等,并介绍了在一个DATA步中创建多个数据集的操作方法;接下来介 绍了对数据集中观测和变量进行操作的多种语句和过程,包括使用SAS 表达式选取部分观测、对观测进行分组和排序、使用选项RENAME=或 RENAME语句更改变量名称、使用赋值语句创建新变量等;随后介绍 了DATA步中的循环语句和数组的用法,以及SAS中常用的数值函数、 字符操作函数、数值与字符转换函数和日期时间相关的函数等;最后一 部分介绍了如何将数据集导出生成多种格式文件的方法。 第4章 对多个数据集的处理 在前面的章节中,已经介绍了对于单个数据集的操作与处理。可在 实际应用中,需要处理和分析的数据往往来自于多个数据源,甚至是逐 天逐月逐年累加起来的。例如一个制造型企业,人力资源部门会收集基 本的员工数据,销售部门会收集员工每月的业绩数据和各种产品每月的 销售数据,生产部门会收集各种产品每天的生产数据和库存数据等。各 个部门的数据通常都是首先存储在不同数据集中的,为了对数据进行全 面分析,常常需要将各种各样的数据进行串接、合并、更新与修改,使 得决策分析所使用的数据具有更加完备的信息。 在这一章中,将介绍如何对两个及多个数据集进行处理,以及对新 产生的数据集进行进一步的操作,并且会比较不同方法下处理多个数据 集的效率。最后,将简要介绍Hash对象及其使用实例。 数据集的纵向串接 4.1 数据集的纵向串接指的是,将两个或者多个数据集首尾相连,形成 一个新的数据集。在图4.1中,将数据集Work.DATA1和Work.DATA2串 接起来,形成了数据集Work.COMBINED。 对数据集的纵向串接可以通过以下两种方法实现: ·使用SAS DATA步的SET语句。 ·使用SAS过程步的APPEND过程。 接下来,将分别通过实例来介绍这两种方法。 4.1.1 使用SET语句实现纵向串接 1.基本形式 使用SET语句实现纵向串接的基本形式如下: DATA 新数据集; SET 数据集1 RUN; 其中: 数据集2 <数据集3 数据集4 …>; ·SET语句中的数据集1、数据集2都为输入数据集。 ·串接后的数据存储在DATA语句的新数据集中。 ·SET语句可以同时读入多个数据集,新数据集将包含各输入数据集 中的所有变量。 图4.1 数据集纵向串接图示 例4.1:对数据集Work.New_Emloyee和Work.Old_Emplyee进行串 接。 以下代码创建了数据集: data work.New_Employee; input Emp_ID $ Emp_Name $ @@; datalines; ET001 Jimmy ED003 Emy EC002 Alfred EQ004 Kim ; run; data work.Old_Employee; input Emp_ID $ Emp_Name $ @@; datalines; EQ122 Molly ET121 Dillon ET123 Helen ED124 John ; run; 数据集Work.New_Emloyee和Work.Old_Emplyee含有相同的变量 Emp_ID和Emp_Name。串接两个数据集的示例代码如下: data work.Employee; set work.Old_Employee work.New_employee ; run; proc print data=work.Old_Employee; title 'Old Employee'; run; proc print data=work.New_Employee; title 'New Employee'; run; proc print data=work.Employee; title 'All Employee'; run; 输出内容如图4.2所示。 图4.2 例4.1打印输出数据集内容 从输出内容中可以观察到新数据集work.Employee包含了2个变量和 8条观测,work.Old_Employee的观测直接加在了work.New_Employee的 后面,名称相同的字段放在同一变量下。 如果Work.New_Employee和Work.Old_Emplyee中含有的变量不全一 致,将这两个数据集进行串接时,情况会是如何呢?以下代码在 Work.New_Emloyee和Work.Old_Emplyee中分别用IF语句创建了新变量 Dept和Gender。 data work.New_Employee_Dept; set work.New_Employee; if substr(Emp_ID,2,1)="T" then Dept="TSG"; else if substr(Emp_ID,2,1)="Q" then Dept="QSG"; else if substr(Emp_ID,2,1)="D" then Dept="DSG"; else if substr(Emp_ID,2,1)="C" then Dept="CSG"; run; data work.Old_Employee_Gen; set work.Old_Employee; if mod(substr(Emp_ID,length((trim(Emp_ID))),1),2)=0 then Gender="F"; else Gender="M"; run; 例4.2:对数据集Work.New_Emloyee_Dept和 Work.Old_Emplyee_Gen进行串接。 示例代码如下: data work.Employee_Update; set work.Old_Employee_Gen work.New_employee_Dept; run; proc print data=work.Employee_Update; title 'All Employee Update'; run; 输出内容如图4.3所示。 图4.3 例4.1打印输出数据集内容 新数据集work.Employee_Update和work.Employee一样含有8条观 测,但是work.Employee_Update含有4个变量,依次为Emp_ID、 Emp_Name、Gender和Dept。由于work.Old_Employee_Gen中不含有 Dept,因此串接后前4条观测的Dept都为缺失值,同样Gender在 work.New_Employee_Dept中不存在,串接后的后4条观测其Gender都为 缺失值。 熟悉SAS DATA步的执行步骤后,就不难理解上述结果了。以下讲 解SAS在遇到SET语句时的执行步骤: 1)第一步为编译阶段,SAS按照SET语句中数据集的排列顺序,依 次读入各数据集中变量的描述部分,并将各变量置入PDV。SAS首先从 work.Old_Employee中找到Emp_ID、Emp_Name、Gender,将它们置入 PDV,然后在work.New_Employee中找到新的变量Gender(Emp_ID和 Emp_Name已经在work.Old_Employee中出现过),并将它置入PDV。 PDV如下: 2)进入执行阶段,SAS按照SET语句中数据集的排列顺序,依次读 入各数据集中的观测。SAS读入work.Old_Employee_Gen中的第1条观 测,并复制到PDV中,由于work.Old_Employee_Gen中没有Dept变量, 所以PDV中该变量值为缺失值。PDV如下: 3)SAS执行DATA步中的其他语句(在本例中无其他可执行语 句),然后将PDV中的值写入新数据集work.Employee_Update中。同 时,PDV中保留从SAS数据集中读入的变量值。在处理完第一条观测 后,PDV如下: 4)SAS读入work.Old_Employee_Gen中的第2条观测,并复制进 PDV,覆盖PDV中各变量值。如果新读入的观测中有缺失值,该缺失值 也会覆盖PDV中原来的变量值。此时的PDV如下: 5)SAS执行DATA步中的其他语句(在本例中无其他可执行语 句),然后将PDV中的值写入新数据集work.Employee_Update中。同 时,PDV中保留从SAS数据集中读入的变量值。在处理完第二条观测 后,PDV如下: 6)SAS按照同样的逻辑处理完work.Old_Employee_Gen中的所有观 测。 7)SAS在开始读入第二个数据集work.New_employee_Dept的第一 条观测前,将PDV初始化,将所有变量值都置为缺失。此时PDV如下: 8)SAS读入work.New_employee_Dept中的第一条观测,并复制进 PDV,同样由于work.New_employee_Dept中没有Gender变量,所以PDV 中该变量值为缺失值。此时PDV如下: 然后执行DATA步中的其他语句,最后将PDV中的值写入新数据集 work.Employee_Update中。同时,PDV中保留了从SAS数据集中读入的 变量值。 9)SAS按照同样的逻辑处理完所有数据集中的所有观测。 了解了上述步骤,就很容易理解上例中的输出结果。 注意 在开始读入每一个数据集的第一条观测前,SAS将对PDV 进行初始化,将其中所有变量值重置为缺失值。这一点很重要,所以在 例4.2中,输出数据集的后4条观测中Gender的取值仍然为缺失值,而非 沿用第4条观测中Gender的取值。 另外,在读入每一个数据集的循环过程中,由SAS数据集中读入的 变量值将被保留在PDV中,直到下一条观测被复制进PDV,它才会被新 的变量值覆盖。而对于DATA步中新创建的变量,在DATA步的每一个 循环前,系统默认将PDV中的值置为缺失。如果要使这些变量值保留在 PDV中,得使用RETAIN语句。 使用RETAIN语句的基本形式为: RETAIN 变量1 < 变量2 变量3 … <初始值>> <变量4 变量3 …>; RETAIN语句的初始值在DATA步的编译阶段进行赋值,在DATA 步执行阶段的循环过程中,RETAIN语句中变量的值可以被保留在PDV 中,并进入下一个循环。如果不指定初始值,系统默认初始值为缺失 值。 如下语句将给变量month1、month2、month3、month4、month5赋 初始值为1,给变量year赋值为0,给变量a、b、c赋值为XYZ。 retain month1-month5 1 year 0 a b c 'XYZ'; 在本章的后面,将会结合例子介绍RETAIN语句的使用场景。 2.使用BY语句进行穿插串接 在例4.2中,如果要让串接后的新数据集中的8条观测按照Emp_ID的 升序排列,该如何操作呢?这时需要引入穿插串接的概念。穿插串接就 是使得新数据集的观测按照一定顺序排列的串接方法。 基本形式如下: DATA 新数据集; SET 数据集1 数据集2 BY 变量1 <变量2 RUN; <数据集3 变量3 数据集4 变量4 … >; … >; BY语句在处理多个数据集时经常使用,为了更好地理解本章后面 的内容,这里需要引入BY变量、BY变量组、BY变量值和BY组合的概 念。 ·BY语句中的变量称为BY变量,一个BY语句中可以有多个BY变 量。 ·当BY语句中有多个变量时,称这些变量为BY变量组。 ·BY变量值指的是BY变量的取值。 ·BY组合指的是具有相同BY变量值的观测的集合。当有多个BY变 量时,BY组合就是使得所有BY变量的取值完全相同的观测的集合。 为了便于理解,在本章后面的阐述中,BY变量既代表一个BY变 量,也可以是多个BY变量。 在使用BY语句时,所有输入数据集都必须是按BY变量排过序,或 者有基于BY变量建立的索引(在以后的所有代码中,使用BY语句都有 这样的要求)。串接完毕后,新数据集中的观测也将按照BY变量排 序。 例4.3:对数据集Work.New_Emloyee_Dept和 Work.Old_Emplyee_Gen进行穿插串接。 由于work.New_Employee_Dept和work.Old_Employee_Gen都没有按 照Emp_ID排序,在对这两个数据集进行穿插串接时,首先必须对其按 Emp_ID排序。对数据集进行排序,可以使用前面章节介绍的SORT过 程。 示例代码如下: proc sort data=work.New_Employee_Dept; by Emp_ID; run; proc sort data=work.Old_Employee_Gen; by Emp_ID; run; data work.Employee_Int; set work.Old_Employee_Gen work.New_Employee_Dept; by Emp_ID; run; proc print data=work.Employee_Int; title 'All Employee Interleaving by ID'; run; 输出内容如图4.4所示。 图4.4 注意 排序后的数据集内容 使用BY语句可以很高效地得到按BY变量排序后的新数据 集。如果新数据集的观测数很多,此方式将会节约资源和时间。 3.使用LENGTH语句 当输入数据集中同名变量的长度不一样时,新数据集中该变量的长 度等于SET语句中第一个数据集中对应变量的长度。在对多个数据集进 行串接时,最好先检查字符型变量的长度,避免在读取变量值时造成截 断。如有需要,可以在使用SET语句之前使用LENGTH语句来定义变量 的长度。 使用LENGTH语句定义变量长度的基本形式如下: LENGTH 变量1 < $ >长度 <变量2 < $ >长度 …>; 在定义字符型变量的长度时,需在长度前面加上$符号。 例4.4:使用LENGTH语句将数据集work.Employee中Emp_ID的长度 定义为15。 示例代码如下: data work.Employee; length Emp_ID $15; set work.New_Employee work.Old_employee; by Emp_ID; run; 4.使用选项RENAME= 在上面的例子中,输入数据集中相同含义的字段正好同名,但是在 很多情况下,由于数据来自于不同的部门系统和时期,输入数据集中相 同含义的变量往往不具有相同的变量名。例如,在一个数据集中表示员 工ID的变量名叫ID,在另一个数据集中表示员工ID的变量名叫 Emp_ID,在进行数据拼接的时候,如果不做另外的处理,这两个变量 将会被SAS认为是两个不同的字段,并分别存储在两个不同的变量下。 这时可以使用RENAME=选项将两个数据集中的变量名改成一致的名 称,避免出现前面描述的问题。 使用RENAME=选项的基本形式如下: 数据集(RENAME= (原变量名1 = 新变量名1 <原变量名2 = 新变量名2 … >)); 例4.5:数据集ex.JMP_Staff和ex.RND_Staff中分别包含了两个系统 中存储的员工信息,现在公司整合员工信息,欲将两个系统中的员工信 息统一管理,并且要求所有员工的记录根据入职年份Entry_Year来排 列。 首先,通过前面介绍的PROC CONTENTS来看一看ex.JMP_Staff和 ex.RND_Staff中分别包含有哪些变量及相应的属性。代码如下: proc contents data=ex.JMP_Staff VARNUM; run; proc contents data=ex.RnD_Staff VARNUM; run; 如图4.5所示是部分输出(VARNUM选项使得CONTENTS过程的输 出中变量按创建时间排序)。 图4.5 PROC CONTENTS过程的部分输出 两个数据集中包含了相同数量的变量,但是数据集JMP_Staff中表 示员工ID的变量名为Employee_ID,而RnD_Staff中表示员工ID的变量名 为Emp_ID,此时可借助RENAME=选项将两个变量的名称改为一致。 示例代码如下: data work.All_Staff; set ex.JMP_Staff (rename=(Employee_ID=Emp_ID)) ex.RnD_Staff; run; 注意 上例中,RENAME=选项并没有真正更改原数据集 ex.JMP_STAFF中的变量名,只是在DATA步的编译阶段,系统读入 ex.JMP_Staff中变量的描述部分时,将变量Employee_ID更名为了 Emp_ID。 4.1.2 使用APPEND过程实现纵向串接 使用APPEND过程进行数据集串接的基本形式如下: PROC APPEND BASE=主数据集 <DATA=追加数据集> <FORCE>; 其中: ·主数据集表示需要增加观测的数据集。该主数据集可以是已经存 在的数据集,也可以是不存在的数据集。当主数据集不存在时,在执行 完APPEND过程后,将生成主数据集,并将追加数据集的观测复制到主 数据集中。 ·追加数据集中包含了需要被添加到主数据集中的观测。这个语句 可以是默认的,此时,SAS会将当前数据集的观测添加到主数据集的后 面。当前数据集指的是SAS系统最近一次生成的数据集。通常情况下, 为了保证程序的可读性,不建议默认。 ·FORCE选项会强制将追加数据集中的观测添加到主数据集中。后 面将会介绍需要使用FORCE选项的3种情况及其结果。 使用APPEND过程进行串接时,SAS不会处理主数据集中的观测, 而是直接将追加数据集的观测添加到主数据集最后一条观测的后面,且 变量仅包含主数据集中的变量。 例4.6:运用APPEND过程将work.New_Employee_Dept和 work.Old_employee_Gen两个数据集进行纵向串接,并将串接后的数据 集保存在work.Old_employee_Gen中(前面在介绍SET语句的时候,已经 介绍并操作过这两个数据集)。 两个数据集的基本情况如图4.6所示。 两个数据集所含的变量不全一致,需要使用FORCE语句来进行串 接,代码如下: proc append base=work.Old_Employee_Gen data=work.New_Employee_Dept FORCE; run; 串接后的数据集work.Old_Employee_Gen如图4.7所示。 图4.6 例4.6中使用的数据集部分内容 图4.7 串接后的数据集部分内容 从上面的例子可以观察到,串接后work.Old_Employee_Gen中仅包 含原来含有的变量,观测数为两个数据集中的观测之和。这里使用了 FORCE选项,如果去掉FORCE选项,串接将失败,这是因为追加数据 集work.New_employee_dept中含有主数据集没有的变量。 使用APPEND过程时特别需要注意FORCE选项的使用,下面介绍需 要使用FORCE选项的3种情况,以及使用FORCE选项后的结果。 ·第一种情况,如果追加的数据集中存在不包含在主数据集中的变 量,使用FORCE选项将会使得串接成功,但仅存在于追加数据集中的 变量不会被添加到主数据集中。 ·第二种情况,如果同名变量在主数据集和追加数据集中的类型不 一样,那么需要使用FORCE选项,但是追加进主数据集的观测对应的 这个变量的值为缺失。 ·第三种情况,如果同名变量在追加数据集中的长度大于主数据集 中的长度,那么需要使用FORCE选项,在执行过程中,追加数据集中 的变量值可能会被截断。 当主数据集中包含追加数据集中不含有的变量时,串接成功,同时 系统会在日志中生成警告,并且追加数据集中不含有的变量之值都为缺 失。另外,当两个数据集中同名变量的属性不一致时,将沿用主数据集 中变量的属性。 在企业交易系统数据的维护过程中可以使用APPEND过程,例如主 数据集可以表示历年的交易记录,追加数据集可以表示每年新增的交易 记录,使用APPEND过程可以将每年新增的销售记录方便快速地串接到 历年的销售记录中。 注意 在使用APPEND过程时,一定要注意观察日志信息,避免 产生数据缺失、截断等异常结果。 4.1.3 SET语句与APPEND过程的比较 对两个数据集进行纵向串接时,如果两个数据集中的变量名称和属 性都相同,使用SET语句和APPEND过程,可以得到完全一样的结果。 但是使用APPEND过程的效率比使用SET语句高,尤其是当主数据集的 观测量很大时,这是因为APPEND过程不对主数据集的观测进行操作, 而是直接把追加数据集的观测加到主数据集的后面。 当输入数据集的个数、所包含的变量或者变量属性不一致时,两种 方法有较大差别,如表4.1所示。 表4.1 SET语句和APPEND过程比较 4.2 数据集的横向合并 数据集的横向合并,指的是将两个或多个数据集根据某种原则横向 合并起来,形成新的数据集。SAS提供了MERGE语句来实现两个或多 个数据集的横向合并。数据的横向合并主要分为以下两种情况: ·不使用BY语句合并,也称为一对一合并。在图4.8中,将数据集 WORK.DATA1和数据集WORK.DATA2进行一对一合并,生成了数据集 WORK.COMBINED。 代码如下: DATA WORK.COMBINED; MERGE WORK.DATA1 WORK.DATA2; RUN; ·使用BY语句合并,也称为匹配合并。在图4.9中,将数据集 WORK.DATA1和数据集WORK.DATA2通过Year进行匹配合并,生成了 数据集WORK.COMBINED。 图4.8 数据集一对一合并图示 图4.9 数据集匹配合并图示 代码如下: DATA WORK.COMBINED; MERGE WORK.DATA1 WORK.DATA2; BY Year; RUN; 4.2.1 不使用BY语句实现横向合并 不使用BY语句进行数据集横向合并的基本形式如下: DATA 新数据集; MERGE 数据集1 RUN; 数据集2 <数据集3 数据集4 … >; 不使用BY语句进行数据集横向合并时,对输入数据集的排序没有 要求。 例4.7:某部门的主管决定在年终的时候对本部门的员工进行考 核,数据集work.staff中包含了员工的基本信息,而另一个数据集 work.schedule中包含了考核时间和地点。现在需要给每位员工分配考核 时间和地点。 以下代码创建了数据集。 data work.staff; infile datalines dsd; length emp_name $20 title $15; input emp_name $ title $ ; datalines; Jacob Adams,Analyst Emily Anderson,Analyst Michael Arnold,Senior Analyst Hannah Baker,Manager Joshua Carter,Senior Analyst ; run; data work.time; input time date9.room $11-24; format time date9.; datalines; 01Dec2013 Meeting Room 1 02Dec2013 Meeting Room 2 03Dec2013 Meeting Room 1 03Dec2013 Meeting Room 2 04Dec2013 Meeting Room 3 04Dec2013 Meeting Room 1 ; run; 其中,Emp_name表示员工姓名,title表示员工的职位,time表示考 核时间,room表示和时间对应的会议室。现在将时间和会议室分配给各 员工,示例代码如下: data work.schedule; merge staff time; run; proc print data= work.schedule; title "Schedule for Performance Management"; run; 输出的内容如图4.10所示。 图4.10 例4.7打印输出数据集内容 下面通过PDV详细讲解SAS执行MERGE语句的步骤: 1)DATA步编译阶段,SAS按照MERGE语句中数据集的排列顺 序,依次读入各数据集中变量的描述部分,并将各变量置入PDV。SAS 将PDV中的所有变量值都置为缺失值。PDV如下: 2)DATA步执行阶段,SAS按照MERGE语句中数据集的排列顺 序,从第一个数据集staff中读入第一条观测,并复制到PDV中。此时的 PDV如下: 3)SAS从第二个数据集time中读入第一条观测,并复制到PDV中。 如果数据集中有同名的变量,后读入的值将覆盖先读入的值。此时的 PDV如下: 4)SAS执行DATA步中的其他语句,然后将PDV中的值写入新数据 集schedule中。 5)SAS依次从各个数据集中读入第二条观测,并复制到PDV中。 PDV如下: 然后SAS执行DATA步中的其他语句,最后将PDV中的值写入新数 据集中。 6)SAS依次处理各个数据集中的各条观测,直到处理完某一个数 据集中的所有观测。在这里,SAS在处理完第一个数据集work.staff中的 第五条观测后,PDV中来自该数据集中的变量都被置为缺失。PDV如 下: 7)SAS读入第二个数据集work.time的最后一条观测,并复制到 PDV中。PDV如下: 8)SAS执行DATA步中的其他语句,然后将PDV中的值写入新数据 集中。此时,SAS已经处理完所有数据集中的所有观测。 这样就不难理解为何新数据集schedule中最后一条观测的变量 emp_name和title的值都为缺失了。 这里可以总结一下SAS处理一对一合并的原则: ·新数据集的第一条观测包含各个输入数据集中第一条观测的信 息,第二条观测包含各个输入数据集中第二条观测的信息,不足的观测 用缺失值补足。 ·新数据集含有的观测数为所有输入数据集的最大观测数。 在实际应用中,一对一合并的情形不常见,多数情况是开发人员在 进行匹配合并时忘记加BY语句。为了防止这种手工错误的发生,SAS 提供了系统选项MERGENOBY=,这样就可在发生这种手工错误时给开 发人员以必要的提醒。MERGENOBY=有以下3种取值: ·MERGENOBY=NOWARN,这是系统的默认选项,在执行没有BY 语句的MERGE语句时,系统进行一对一合并。 ·当MERGENOBY=WARN时,系统给出警告,“WARNING:没有 为MERGE语句指定BY语句。” ·当MERGENOBY=ERROR时,系统报错,“ERROR:没有为 MERGE语句指定BY语句。” 在例4.7中,如果在程序中重新设置系统选项 MERGENOBY=ERROR,程序将报错,如下: options mergenoby=error; data work.schedule; merge staff time; run; 日志信息如图4.11所示。 图4.11 4.2.2 报错日志 使用BY语句实现横向合并 使用BY语句进行数据集横向合并的基本形式如下: DATA 新数据集; MERGE 数据集1 数据集2 <数据集3 数据集4 BY 变量名1 <变量名2 变量名3 … >; RUN; … >; 和之前介绍SET语句时提及的一样,当使用BY语句时,输入数据集 必须按BY变量排序。 SAS在处理匹配合并的DATA步程序时,主要分以下两个阶段: ·编译阶段,SAS在辨识出MERGE语句后,将按照其中数据集的排 列顺序,依次读入所有数据集中变量的描述部分,包括DATA步中新创 建变量的描述部分,并将所有变量置于PDV中。若不同的数据集中有同 名的变量,则要求它们的数据类型必须相同,长度以第一次出现的变量 为准。 ·执行阶段,各输入数据集中的观测按BY变量进行匹配,在DATA 步的每次循环中依次读入BY组合的每条观测。如果遇到同名的变量, 后读入的变量值将覆盖先读入的变量值。由数据集中读入的变量值,会 自动地保留到BY变量值在所有输入数据集中都改变为止。由DATA步新 创建的变量,其变量值在每次循环中都不能在PDV中自动保留,也就是 说,在处理下一条数据之前它将被置为缺失值。 匹配合并数据集在数据处理中应用非常广泛,只有理解了SAS处理 匹配合并的逻辑之后才能在使用的时候进行合理的操作。接下来逐一介 绍匹配合并的3种情形,并结合PDV讲解SAS的处理逻辑。 1.BY变量值在所有数据集中唯一 BY变量值在所有数据集中都是唯一的,即在任一输入数据集中, 没有两条或两条以上的观测具有相同的BY变量值,这是最简单的一种 情景。 例4.8:数据集ex.staff_personel中包含了员工的基本信息,如 Emp_ID、姓名、部门,另一个数据集ex.sales_current_month包含了员工 本月的业绩信息,如Emp_ID、产品种类和销售额,但是不含有员工姓 名。现在欲制作一张报表展现员工本月业绩,并在报表中包含员工姓名 和部门数据。 首先得对ex.staff_personel和ex.sales_current_month按照Emp_ID进行 排序,代码如下: proc sort data=ex.staff_personel out=work.staff_personel; by Emp_ID; run; proc sort data=ex.sales_current_month out=work.sales_current_month; by Emp_ID; run; 排序后的数据集如图4.12所示。 图4.12 例4.8数据集排序后部分内容 以下代码实现了合并操作: data work.Staff_sales; merge work.staff_personel work.sales_current_month; by Emp_ID; run; proc print data=work.Staff_sales noobs; title'Staff Sales'; run; 输出内容如图4.13所示。 图4.13 例4.8打印输出数据集部分内容 当BY变量值在所有数据集中都唯一时,各个数据集的每个BY组合 中都只有一条观测。结合PDV来讲解SAS的处理步骤。 1)在DATA步的编译阶段,SAS按照MERGE语句中数据集的排列 顺序,依次读入各数据集变量的描述部分,并将各变量置入PDV。SAS 将PDV中的所有变量值都置为缺失值。PDV如下: 2)SAS根据各数据集BY变量值的第一个值确定排在第一的BY组 合。在本例中,两个数据集中第一个BY组合的BY变量值都为ED002。 3)SAS根据MERGE语句中数据集的排列顺序,从第一个数据集 work.staff_personel中读取第一个BY组合中的观测,并复制到PDV中。 PDV如下: 4)SAS从第二个数据集work.staff_current_month中读取相应BY组合 中的观测,并复制到PDV中。如果某一个数据集中没有任何观测属于该 BY组合,则仅包含于该数据集的变量在PDV中为缺失值。PDV如下。 5)SAS执行DATA步中的其他语句(本例中没有其他语句),然后 将PDV中的值写入新的数据集中。这时所有数据集中都不再有观测属于 第一个BY组合,SAS将PDV中所有变量值都重置为缺失。PDV如下: 6)SAS从各数据集中确定排在第二的BY组合。两个数据集中下一 个BY组合的BY变量值都为EQ002。SAS重复第三步至第五步,处理第 二个BY组合。处理完后,PDV中的变量值都置为缺失。 7)SAS从各数据集中处理第三个BY组合。work.staff_personel中下 一个BY组合的BY变量值为ET005,work.staff_current_month中下一个 BY组合的BY变量值为ET003,则SAS确定BY变量值为ET003的BY组合 为第三个BY组合。 8)work.staff_personel中没有观测属于第三个BY组合(BY变量为 ET003的观测),于是仅在work.staff_personel中存在的变量在PDV中将 为缺失值,SAS接着从work.staff_current_time中读取第三个BY组合的观 测,并复制到PDV中。此时的PDV如下: 9)SAS执行DATA步中的其他语句,然后将PDV中的值写入新数据 集,并将PDV中的变量值重置为缺失。 10)SAS按照以上的逻辑执行,直到处理完所有数据集的所有观 测。 新数据集work.Staff_Sales中包含了两个数据集中的所有变量,因为 Emp_ID为EC001的记录在work.staff_personel中不存在,所以无法知道该 员工的姓名及部门信息;又因为Emp_ID为ET009的记录在 work.sales_current_month中不存在,所以无法知道该员工的业绩信息。 2.BY变量值在某一数据集中存在重复 BY变量值在某一输入数据集中存在重复值,即在其中一个输入数 据集中,含有两条或两条以上的观测具有相同的BY变量值,也称为一 对多合并。在匹配过程中会遵循如下原则:由输入数据集读入的变量 值,会保留在PDV中,直到被下一个读入的观测值覆盖或该BY组合处 理完毕被重置为缺失值为止。接下来通过一个简单的例子来具体讲解这 一原则。 例4.9:接着例4.8,数据集ex.sales_three_month中包含了最近3个月 的绩效信息,每个员工每个月都有一条记录,现在欲制作一个报告显示 员工最近3个月的绩效信息,并显示员工的姓名和部门。 这里Emp_ID变量值在输入数据集中不再是唯一的。以下代码可以 实现员工信息和最近3个月绩效信息的匹配: proc sort data=ex.staff_personel out=work.staff_personel; by Emp_id; run; proc sort data=ex.sales_three_month out=work.sales_three_month; by Emp_id; run; data work.staff_report; merge work.staff_personel work.sales_three_month; by Emp_id; run; proc print data=work.staff_personel noobs; title "Staff Information"; run; proc print data=work.sales_three_month noobs; title "Sales For Three Months"; run; proc print data=work.staff_report noobs; title "Staff Report"; run; 输出内容如图4.14所示。 当BY变量值有重复时,则在某些数据集中的某些BY组合中会存在 多条观测。SAS在碰到这种情况时的处理步骤如下: 图4.14 例4.9打印输出数据集内容 1)DATA步编译阶段,SAS按照MERGE语句中数据集出现的排列 顺序,依次读入各数据集变量的描述部分,并将各变量置入PDV。 Emp_ID、Emp_Name和Dept来自于work.staff_personel,Product_Class、 Sales和month来自于work.sales_three_month。SAS将PDV中所有变量值 置为缺失。PDV如下: 2)SAS根据各数据集BY变量的第一个值确定排在第一的BY组合。 在本例中,两个数据集中第一个BY组合的BY变量值都为ED002。 3)SAS根据MERGE语句中数据集的顺序,从第一个数据集 work.staff_personel中读取第一个BY组合中的第一条观测,并复制到 PDV中。此时,PDV如下: 4)SAS从第二个数据集work.sales_three_month读取相应BY组合 (BY变量值为ED002的观测的集合)中的第一条观测,并复制到PDV 中。此时,PDV如下: 5)SAS将变量值从PDV中写入新数据集,并且同时在PDV中保留 这些变量值。但是请注意,DATA步中新创建的变量值在从PDV中写入 新数据集后,将被重新置为缺失值。在本例中,由于没有新创建的变 量,PDV中的值仍然与步骤4所列的相同。 6)SAS从各个数据集中读取第一个BY组合中的第二条观测。此 时,work.staff_personel不含有第二条属于该BY组合的观测,所以SAS不 从work.staff_personel读取观测,Emp_Name和Dept沿用PDV中保留的变 量值。接着,SAS从work.sales_three_month中读取第二条观测,并复制 到PDV中。PDV如下: 7)SAS将变量值从PDV中写入新数据集,并且同时在PDV中保留 这些变量值。 8)SAS从各数据集中读取第一个BY组合的第三条观测。此时,只 有work.sales_three_month中含有第三条观测,则SAS从 work.sales_three_month中读取第三条的观测,并复制到PDV中。PDV如 下: 9)SAS将变量值从PDV中写入新数据集。此时,所有输入数据集 中都不再含有属于第一个BY组合的观测,也就是不再含有BY变量为 ED003的观测,SAS将PDV中所有变量值都置为缺失。PDV如下: 10)SAS从各数据集中寻找下一个BY组合。如果work.staff_personel 的下一个BY组合的BY变量值为EQ001,work.sales_three_month的下一 个BY组合的BY变量值为ET009,则SAS判断BY变量值为EQ001的BY组 合为下一个BY组合。 11)SAS从work.sales_personel中读取BY变量值为EQ001的BY组合 的第一条观测,并复制到PDV中,由于work.sales_three_month中的观测 都不属于该BY组合,则来自于work.sales_three_month的变量 Product_Class、Sales和Month的值都为缺失。此时,PDV如下: 12)SAS将变量值从PDV中写入新数据集,由于所有数据集中都不 再含有属于第二个BY组合的观测,因此SAS将PDV中的所有变量值都 置为缺失。 13)SAS从各数据集中寻找下一个BY组合并重复上面的操作,直 到处理完所有数据集中所有观测。 从输出结果可以看出,在新数据集中,每个BY变量值重复的次数 和输入数据集中重复次数多的一样。 3.BY变量值在多个数据集中存在重复 虽然在匹配合并时,一般情况下BY变量值至多在某一个数据集中 有重复,但并不代表匹配合并只能处理这一种情况,它同样可以处理两 个或两个以上输入数据集中的BY变量值重复的情况,也就是实现多对 多合并。SAS的匹配原则和一对多合并时一样,并且新数据集中每一个 BY变量值重复的次数和输入数据集中重复次数最多的一样。 运用MERGE语句进行多对多合并在实际应用中并不常见,但是理 解了SAS的匹配原则在实际应用中有助于开发人员进行程序调试和质量 控制。这里用一个简单的例子帮助读者理解。 在输入数据集中X=1的观测有3条记录,X=2的观测有2条记录,输 出数据集中X=1和X=2的观测也分别是3条和2条。读者可以按照例4.8介 绍的方法一步步分解SAS的处理步骤。 4.2.3 使用数据集选项IN=操作观测 在例4.8中,work.staff_personel数据集中有一部分员工没有业绩信 息,如果不想将这些员工的信息包含在新生成的数据集中,就需要确定 数据集work.staff_personel与work.sales_current_month是否分别输出了它 们的观测值到输出数据集中。使用数据集选项IN=可以帮助实现这一功 能。 数据集选项IN=的基本形式如下: 数据集(IN= 变量) 数据集选项IN=可以运用在SET、MERGE、MODIFY、UPDATE语 句中的任何数据集后面。变量是数值型临时变量,不同的数据集应定义 不同的临时变量名称,临时变量可以在DATA步中使用,但是不会在数 据集中输出。在某一数据集后面使用(IN=变量)时,如果PDV中对应 的变量值是来自于这一数据集的观测,临时变量将被赋值为1,否则临 时变量的值为0。特别是,当和BY语句一起使用时,可以通过判断PDV 中BY变量的值是否来自于这一数据集来确定临时变量的值。 以下程序对例4.8的数据集进行匹配合并时使用了数据集选项IN=。 由于选项IN=指定的只是临时变量,为了在输出数据集中能看到变量的 值,因此在程序中又将这些临时变量的值赋给了新变量INA和INB。从 输出结果可以看出,在合并以后的数据集中,来自work.staff_personel的 观测的INA为1,来自sales_current_month的观测的INB为1。 data work.Staff_sales; merge work.staff_personel (IN=a) work.sales_current_month (IN=b); by Emp_ID; ina=a; inb=b; run; proc print data=work.Staff_sales noobs; title 'Staff Sales'; run; 输出如图4.15所示。 图4.15 例4.8使用选项IN=打印输出内容 如果只想将既存在于work.staff_personel又存在于 work.sales_current_month中的员工记录写入新数据集中,可以通过IF语 句实现。 data Staff_sales; merge staff_personel (IN=a) sales_current_month (IN=b); by Emp_ID; if a and b; run; 其中“if a and b;”是“if a and b then output;”的一种省略形式。 数据集的更新 4.3 数据集的更新,指的是用一个数据集中的数据来替换另一个数据集 中的数据。SAS提供了UPDATE语句来实现数据集的更新,如图4.16所 示。 图4.16 UPDATE语句如下: DATA WORK.DATA1; UPDATE WORK.DATA1 WORK.DATA2; BY Year; RUN; 数据集更新图示 在运用UPDATE语句进行数据集更新时,通常称DATA1为主数据 集,DATA2为更新数据集。在上述示例中,主数据集和更新数据集通 过BY变量Year联系起来,更新数据集中的非缺失的变量值替换了主数 据集中的变量值。对于更新数据集中存在缺失值的情况,在UPDATE语 句中可以运用选项UPDATEMODE=来控制是否用缺失值替换主数据集 中的变量值,系统默认UPDATEMODE=MISSINGCHECK,也就是不替 换;如果设置UPDATEMODE=NOMISSINGCHECK,则不管更新数据 集中的变量值是否是缺失值,都将替换。 使用UPDATE语句进行数据集更新的基本形式如下: DATA 新数据集; UPDATE 主数据集 更新数据集 <UPDATEMODE = MISSINGCHECK | NOMISSINGCHECK>; BY 变量1 <变量2 变量3 …>; RUN; 更新后数据集的名称可以是新的数据集名称,不需要和主数据集同 名。新数据集中包含主数据集和更新数据集中的所有变量。 主数据集和更新数据集都必须按照BY变量排序。通常要求BY变量 值在主数据集中必须是唯一的,且不需要进行更新,例如BY变量可以 是员工号或交易号。运用UPDATE语句时,如果SAS发现主数据集中BY 变量值有重复,SAS会在日志中输出警告,此时虽然可更新成功,但是 所有的更新只会作用在主数据集中BY组合的第一条观测上。 例4.10:某公司的市场部门将所有的客户信息都存储在数据集 work.Customer中,但是每年都需要对这些客户信息进行及时更新,更新 信息存储在数据集work.UpdateInfo中。 以下代码创建了数据集work.Customer和work.UpdateInfo。 work.Customer和work.UpdateInfo中包含了客户编号、姓名、地址、联系 方式等数据。 data work.Customer; input Customer_ID $1-4 Name $ 6-19 Address $ 21-37 City $ 39-51 State $ 53-54 ; datalines; C001 Jacob Adams 111 Clancey Court Chapel Hill NC C002 Emily Anderson 1009 Cherry St. York PA C003 Michael Arnold 4 Shepherd St. Vancouver BC C004 Hannah Baker Box 108 Milagro NM ; run; proc print data=work.Customer noobs; title "Customer - Master Data"; run; data work.UpdateInfo; infile datalines missover; input Customer_ID $1-4 Name $ 6-19 Address $ 21-37 City $ 39-51 State $ 53-54 ; datalines; C001 14 Bridge St. San Francisco CA C002 Emily Cooker 42 Rue Marston C002 52 Rue Marston Paris C005 Jimmy Cruze Box 100 Cary NC ; run; proc print data=work.UpdateInfo noobs; title "Update Information - Yearly Transaction Data"; run; 创建的数据集内容如图4.17所示。 图4.17 例4.10使用的数据集内容 在图4.17中,Customer_ID是客户的注册号,对于客户来说,这个注 册号是唯一的。由于work.Customer和work.UpdateInfo都已经按照 Customer_ID排过序,因此在更新前不需要重新排序。接下来用 UPDATE语句对数据集work.Customer进行更新。示例代码如下: data work.Customer_update; update work.Customer work.UpdateInfo; by Customer_ID; run; proc print data=work.Customer noobs; title "Customer - Master Data Update"; run; 例4.10更新后的数据集如图4.18所示。 图4.18 例4.10更新后的数据集内容 在上例中,主数据集中的BY变量是唯一的,更新数据集中的BY变 量值存在重复值,我们可以有如下发现: ·当更新数据集中BY变量值存在重复值时,BY组合中的后一条观测 会覆盖前一条观测。例如Customer_ID为C002的更新信息分别记录在 work.UpdateInfo的两条观测中,更新后的数据集中Customer_ID为C002 的观测是唯一的,并且Address为work.UpdateInfo中后一条观测的值。 ·当更新数据集中的某一条观测在主数据集没有对应的观测时,SAS 会直接将这条观测作为更新的基础,然后结合更新数据集中剩余的观测 继续处理该观测。如上例中Customer_ID为C005的观测在主数据集中不 存在,SAS将其作为了更新的基础,由于更新数据集中不含有其他 Customer_ID为C005的观测,所以该条观测被直接加入新数据集中。 UPDATE语句和MERGE语句都可以对两个数据集进行匹配合并, 但是存在比较大的区别,如下: ·UPDATE语句只能操作两个数据集;MERGE语句可以对两个或两 个以上数据集进行操作。 ·使用UPDATE语句时必须使用BY语句;MERGE语句在不使用BY 语句的时候也可以按观测号进行一对一合并。 ·在处理缺失值时,UPDATE语句可以控制是否用缺失值对主数据 集进行替换;MERGE语句中后一数据集中的缺失值一定会覆盖前一数 据集中的值。 ·当BY变量值在后一数据集或者更新数据集中不唯一时,UPDATE 语句和MERGE语句的处理方式不一样,详见上一章节中MERGE语句的 多种情形和本节前面部分的介绍。 数据集的更改 4.4 SAS提供了MODIFY语句进一步扩充了DATA步的功能,它可以在 原数据集上直接进行替代、删除或添加观测的操作。 4.4.1 单个数据集的更改 运用MODIFY语句进行单个数据集更改的基本形式如下: DATA 原数据集; MODIFY 原数据集; RUN; 运用MODIFY语句,可以更改原数据集中任何变量的值,但是由于 该语句不能够更改原数据集的描述部分,因此不能在原数据集中添加或 者删除变量。以下通过一个销售管理系统的例子来讲解MODIFY语句的 运用。 数据集work.inventory中包含产品、库存和价格的信息,以下代码创 建了数据集work.inventory。 data work.inventory; input Product_ID $ Instock Price; datalines; P001R 12 125.00 P003T 34 40.00 P301M 23 500.00 PC02M 12 100.00 ; proc print data=work.inventory noobs; title "Warehouse Inventory"; run; 数据集work.inventory内容如图4.19所示。 例4.11:由于物价上涨导致成本上升,公司决定将每种产品的销售 价格提高15%。 示例代码如下: data work.inventory; modify work.Inventory; price=price*1.15; run; proc print data=work.inventory noobs; title 'Price reflects 15% increase'; run; 更改后的数据集内容如图4.20所示。 图4.19 数据集work.inventory内容 图4.20 注意 更改后数据集work.inventory内容 以上对work.inventory的操作,其实也可以通过SET语句来 实现。对于单个数据集的操作,MODIFY和SET语句相比较,由于 MODIFY语句是在没有备份的情况下直接操作原数据集的,因此更加节 省磁盘空间。 4.4.2 两个数据集的更改 使用MODIFY语句可以实现通过一个数据集对另一个数据集中的数 据进行更改,基本形式如下: DATA 主数据集; MODIFY 主数据集 修改数据集; BY 变量1 <变量2 变量3 RUN; …>; 其中,主数据集表示需要更改的数据集,修改数据集中包含了用于 更改的数据,此时必须使用BY语句。在使用MODIFY时,主数据集和 修改数据集中的BY变量不需要事先排序或索引,但是按BY变量排序或 索引可以提高运行效率,特别是在观测量很大的情况下。 在使用MODIFY更改数据集时,需要注意以下两点: ·当主数据集中的BY变量值有重复值时,只有每个BY变量值的第一 条观测会被更改;当修改数据集中的BY变量值有重复值时,系统会依 次读入修改数据集中的每一条观测,并应用到主数据集,且后读入的观 测会覆盖前一次读入的观测;当主数据集和修改数据集中的BY变量值 都有重复值时,系统会依次操作修改数据集中BY组合中的每一条观 测,并且应用到主数据集中对应BY组合的第一条观测中。 ·如果在修改数据集中存在缺失值,系统默认不用缺失值更改主数 据集中的数据。如果需要用缺失值更改主数据集,可以仿照在UPDATE 语句中使用选项UPDATEMODE=实现操作。 实际上,在主数据集或修改数据集中BY变量值有重复值时, MODIFY语句的处理逻辑和UPDATE语句一样。 例4.12:接着例4.11继续操作,数据集work.inventory2中保存了公司 产品在海外仓库的库存信息,现在公司财务欲根据work.inventory和 work.inventory2中的数据计算公司各产品的总库存,并制作报告。 以下代码创建数据集work.inventory2。 data work.inventory2; input Product_ID $ Outstock ; datalines; P001R 12 P001R 30 P001R 25 P003T 34 P301M 23 ; proc print data=work.inventory noobs; title "Warehouse Inventory Inhouse"; run; proc print data=work.inventory2 noobs; title 'Warehouse Inventory Overseas'; run; 两个数据集work.inventory和work.inventory2的内容如图4.21所示。 需要注意的是,在work.inventory2中,产品P001R有三条观测。 计算各产品总库存的程序如下: data work.inventory; modify work.inventory work.inventory2; by product_id; instock=instock+Outstock; run; proc print data=work.inventory noobs; title 'Total Inventory'; run; 更改后的数据集work.inventory的内容如图4.22所示。 图4.21 图4.22 两个数据集内容 更改后的数据集work.inventory的内容 程序中加入“instock=instock+Outstock;”用来计算instock并输出到 数据集work.inventory中,产品P001R的instock的值等于原work.inventory 数据集中P001R的instock值加上work.inventory2中P001R的3条观测的 instock值。 以上例子中,修改数据集中的所有观测在主数据集中都可以找到对 应的观测,当修改数据集中含有一些新观测时,可以使用OUTPUT语句 将新观测添加到主数据集中,具体使用方法请查询SAS帮助文档。 数据集处理的一点补充 4.5 4.5.1 使用数据集选项END= 很多情况下,在进行数据操作时需要知道SAS在什么时候处理输入 数据集的最后一条观测,这时可以使用SAS提供的数据集选项END=来 帮助辨识。使用数据集选项END=的基本形式如下: SET 数据集1 < 数据集2 数据集3 … > END=变量; 在使用SET、MERGE、MODIFY、UPDATE语句时,都可以使用该 选项。这里语句中的变量是数值型的临时变量,当DATA步在操作数据 集的最后一条观测时,该变量的取值为1,否则为0。临时变量可以在 DATA步中使用,但是不会在数据集中输出。需要特别注意的是,在使 用SET、MERGE、MODIFY、UPDATE语句进行多个数据集处理时,只 有当DATA步处理所有输入数据集的最后一条观测时,该变量的取值才 会由0变成1。 例4.13:数据集work.Sales中记录了公司每位员工的全年的销售额, 现在欲计算全年公司的总销售额,并输出到报表中。 以下代码创建了work.Sales数据集,Emp_ID表示员工编号,Dept表 示员工所属部门,Sales表示销售额。 data work.sales; input Emp_ID $ Dept $ Sales Gender$; datalines; ET001 TSG 100 M ED001 DSG 200 F ED001 DSG 135 M EQ001 QSG 234 F ET001 TSG 125 F ET002 TSG 98 M EQ002 QSG 100 M EQ003 QSG 98 M ED002 DSG 124 M ET003 TSG 123 F ; run; proc print data=work.sales; title 'Sales'; run; 数据集work.sales的内容如图4.23所示。 图4.23 数据集work.sales内容 现在运用END=选项计算公司全年的总销售额。 data work.total_sales; set work.sales END=last; total_sales+sales; if last then output; keep total_sales; run; proc print data=work.total_sales noobs; title 'Total Sales in Million'; run; 输出内容如图4.24所示。 图4.24 例4.13打印输出work.total_sales内容 上面这段代码中有以下几个要点: ·END=last定义了临时变量last,当SAS在处理最后一条观测时,last 的取值将变为1。 ·“total_sales+sales;”计算公司的全年销售 额。“total_sales+sales;”的作用和“retain total_sales 0; total_sales=total_sales+sales;”一样。 ·IF语句和OUTPUT语句判断了最后一条观测,并将该观测输出到数 据集中。 ·KEEP语句仅将需要输出的total_sales变量保留在数据集中。 上面计算全年销售额的代码和下面的代码等价。 data work.total_sales; set work.sales END=last; retain total_sales 0; total_sales=total_sales+sales; if last then output; keep total_sales; run; 4.5.2 使用自动变量FIRST.与LAST. 在DATA步中若使用了BY语句,SAS要求读入的数据集必须按BY 变量排序(MODIFY语句除外),同时,SAS在程序执行过程中会自动 生成两个数值型临时变量:FIRST.变量和LAST.变量,它们分别用来辨 识BY组合的第一条观测和最后一条观测。当只有一个BY变量时,很容 易理解它的取值。 ·当DATA步正在处理该BY变量值的第一条观测时,FIRST.变量为 1,否则为0。 ·当DATA步正在处理该BY变量值的最后一条观测时,LAST.变量 为1,否则为0。 当有多个BY变量时,系统会自动生成多对FIRST.变量和LAST.变 量。例如,SAS在执行如下代码时,会自动生成临时变量FIRST.x和 LAST.x,以及FIRST.y和LAST.y。 data work.test; input x y $ @@; datalines; 1 A 1 A 1 B 2 B 2 C ; run; data work.try; set work.test; by x y; firstx=first.x; firsty=first.y; run; lastx=last.x; lasty=last.y; 数据集work.try如图4.25所示。 图4.25 数据集work.try内容 当SAS处理第一条x=1的观测时,first.x=1;当SAS处理最后一条 x=1的观测时,last.x=1;当SAS处理第一条x=1并且y=“A”的观测时, first.y=1;当SAS处理最后一条x=1并且y=“A”的观测时,last.y=1。 例4.14:数据集work.Sales中包含了每位员工的销售额和员工所属部 门及性别数据,现在公司欲统计各部门男员工和女员工的总销售额,并 制成报表。 首先将work.Sales数据集按Dept和Gender进行排序,然后再运用选 项FIRST.与LAST.计算各部门男女员工的总销售额。 proc sort data=work.sales; by Dept Gender; run; data work.sales_dept; set work.sales; by Dept Gender; retain sales_by_dept; if first.Gender then sales_by_dept=0; sales_by_dept=sales_by_dept+sales; if last.Gender; keep Dept Gender sales_by_dept; run; proc print data=work.sales_dept noobs; title 'Sales by Department by Gender'; run; 输出内容如图4.26所示。 图4.26 例4.14打印输出内容 “if last.gender;”是“if last.gender then output;”的省略写法。这里使 用了RETAIN语句,使得在DATA步的循环中sales_by_dept的值可以保留 在PDV中。 BY语句创建的这两个临时变量不仅可在SET语句读入单个数据集时 使用,在进行多个数据集的拼接时也常使用。 4.5.3 使用SET语句中的选项POINT=和NOBS= 在DATA步使用SET语句读入数据集时,可使用选项POINT=指明要 读入的观测序号。使用选项POINT=的基本语法如下: SET 数据集 POINT=指针变量; 其中指针变量用来指明要读入特定序号的观测,必须在SET语句执 行前对它赋值。在有SET语句的DATA步程序中,系统将反复执行 DATA步的语句,直到遇到数据中的文件结束标志。但是在使用选项 POINT=时,系统会直接读入指针指向的记录,这就很可能导致系统不 会遇到文件结束标志,容易陷入死循环。因而在按指针读入数据的程序 中,经常会用到STOP语句。 如果要取得数据集中观测的个数,可以使用SET语句中的选项 NOBS=,语法如下: NOBS=变量; 其中,变量同样是临时变量,在DATA步的编译阶段赋值。 例4.15:现有一个数据集work.Whole,欲从其中随机抽取1/3的观测 用来建立模型。 示例代码如下: data work.sample; do i=1 to total by 3; set work.whole point=i nobs=total; output; end; stop; run; 运行这个代码,系统将从数据集work.sample中依次抽出第1条观 测、第4条观测…,并存储到数据集work.sample,直到系统处理完数据 集中的全部观测。 4.5.4 使用多个SET语句 在SAS中实现同一个操作,往往可以有多种方法。作为补充,这里 将介绍如何运用SET语句进行数据集横向合并。使用SET进行横向合并 的一个好处是,在合并之前不需要将输入数据集进行排序。 例4.16:以下是使用多个SET语句的一个简单例子。 data work.data1; input x y @@; datalines; 1 2 2 3 3 4 ; run; data work.data2; input x z @@; datalines; 1 3 3 5 4 2 5 4 ; run; data work.combined; set work.data1; set work.data2; run; proc print data=work.Combined; title 'Using Two Set in Data Step'; run; 数据集work.data1和数据集work.data2的内容如图4.27所示。 合并后的数据集work.combined如图4.28所示。 图4.27 数据集work.data1和work.data2的内容 图4.28 合并后的数据集内容 在使用多个SET语句时,PDV中的变量是读入数据集的并集。首 先,DATA步从第一个SET语句中读入第一条观测,并复制到PDV中; 然后从第二个SET语句中读入第一条观测,并复制到PDV中。当输入数 据集中有同名变量时,后读入的数据集的变量值覆盖前一个数据集中同 名变量的值。新数据集中的观测数是所有输入数据集中的观测数的最小 值,因为当DATA步遇到第一个文件结束标志时,DATA步就结束执 行。 例4.17:在sashelp.class中记录了一个班级学生的姓名、性别、年 龄、身高和体重信息,现在针对每个小朋友,欲找出比其年龄大的小朋 友的姓名和年龄,并制作成报表展现出来。 示例代码如下: data work.class_expand; set sashelp.class(keep=name age); do i=1 to nobs; set sashelp.class(keep=name age rename=(name=nameolder age=ageolder)) nobs=nobs point=i; if age lt ageolder then output; end; run; proc sort data=work.class_expand; by name age ageolder nameolder; run; proc print data=work.class_expand; title "Students"; by name age; id name age; run; 图4.29是部分输出报告。 图4.29 例4.17打印输出内容 这里在PRINT过程中使用了BY语句和ID语句,BY语句可使报表按 BY变量组输出列表,ID语句中的变量用来代替观测序号,置于输出的 最左侧。PRINT过程将在第5章中详细介绍。 4.5.5 使用HASH对象处理多个数据集 本章的前面部分已经介绍了运用SET语句和MERGE语句进行数据 集之间的拼接,这里将通过实例介绍如何运用HASH对象实现这类操 作。相较于SET语句和MERGE语句,使用HASH对象有两个优点,第 一,它对数据集的排序没有要求;第二,由于HASH对象是放入内存中 的数据集,因此在内存允许的情况下,运用HASH对象进行数据集之间 的关联时其效率更高。 一般在观测数很多的数据集与观测数较少的数据集进行匹配时,运 用HASH对象将较小的数据集加载到内存中,可以非常快速地完成匹 配。 接下来首先介绍在DATA步中如何定义HASH对象。 1.定义HASH对象 使用HASH对象时,首先要定义HASH对象,基本形式如下: DECLARE object HASH对象名(<主题1: 内容1 <, 主题2: 内容2, …>>); HASH对象名.DEFINEKEY(<主题1: 内容1 <, 主题2: 内容2, …>>); HASH对象名.DEFINEDATA(<主题1: 内容1 <, 主题2: 内容2, …>>); HASH对象名.DEFINEDONE(); 这里第一行定义了HASH对象名称,object可以是HASH或HITER, HASH对象名由编程者定义。 第二行和第三行分别通过HASH对象名.method的语法定义了HASH 对象的KEY变量和DATA变量,这些变量都是DATA步中的变量。 第四行表明HASH对象定义结束。 例4.18:现在有一个数据集work.Sales,其中包含了员工编号 Emp_ID、员工部门Dept、销量Sales、产品编号Product_id,另一个数据 集work.Product中包含了Product_id、Product_name和Description 3个变 量,现在欲将Product_name和Description加到数据集Sales中。 以下代码生成Sales和Product中的部分数据。 data work.sales; input Emp_ID $ Dept $ Sales Product_ID $; datalines; ET001 TSG 100 P001 ED001 DSG 120 P001 ET001 TSG 50 P002 EC001 CSG 230 P004 EQ001 QSG 150 P003 ET001 TSG 210 P004 ET002 TSG 40 P002 ET003 TSG 150 P003 ; run; data work.product; infile datalines dsd; length Product_ID $4 Product_Name Description $35; input Product_ID $ Product_Name $ Description $; datalines; P001,ManufacturingIndustry Solutions,ManufacturingIndustry Solutions V2 P002,Logistics Solutions,Logistics Solutions V1 P003,Financial Service Solutions,Financial Service Solutions V3 P004,Insurance Solutions,Insurance Solutions V2 ; run; 以下是通过HASH对象实现匹配的完整代码。 data work.sales_description(drop=ErrorDesc) work.exception; length Product_ID $8 Product_Name Description $35; if _N_=1 then do; /*Define hash objective*/ declare hash product_desc(dataset:"work.product"); product_desc.definekey('Product_ID'); product_desc.definedata('Product_Name','Description'); product_desc.definedone(); call missing(Product_ID,Product_Name,Description); end; set work.sales; /*Retrieve matching data*/ rc=product_desc.find(); if rc = 0 then output sales_description; else do; ErrorDesc="No Product Description"; output exception; end; drop rc; run; SAS处理以上程序的步骤如下: 1)在编译阶段,SAS读入数据集work.Sales中变量及新创建的变量 的描述部分,并将其置于PDV中。PDV如下: 2)在执行阶段,当_N_=1时,定义HASH对象并加载数据到HASH 对象中。Call Missing()语句可以消除日志中部分变量没有初始化的信 息。此时,PDV如下: HASH对象product_desc如下: 3)读入work.Sales数据集的第一条观测,并置入PDV。PDV如下: 4)系统调用product_desc.find()在Hash对象中检索数据。 product_desc.find()可以指定Sales数据集中的KEY变量,当未指定任何 KEY变量的时候,系统默认Sales数据集的KEY变量和HASH对象的KEY 变量同名。当系统检索到HASH对象中存在的某个KEY值和PDV中KEY 变量的值相等时,则返回代码rc的取值为0,同时将HASH对象中对应的 DATA变量的值读入PDV中。此时,PDV如下: 5)系统判断rc=0,将PDV中的数据写入数据集 work.sales_description中。 6)系统读入Sales数据集的第二条观测,并重复第三步到第五步的 操作,直到系统读入Sales数据集中的所有观测。 处理后的数据集work.Sales_Description内容如图4.30所示。 图4.30 注意 处理后的数据集work.Sales_Description内容 在上述程序中,笔者将没有找到Product_Name和 Description的销售记录输出到了数据集work.exception中,并且创建了一 个新变量保存未匹配成功的原因。这种方法便于开发人员调试程序,找 到数据中存在的问题。 2.使用.REPLACE操作 使用.REPLACE可以替换已经加载入内存中的HASH对象的数据。 基本形式如下: HASH对象名.REPLACE<主题1: 内容1 <, 主题2: 内容2, …>>); 例4.19:公司现有库存1000台汽车,每台车可以发往全国各个仓 库,但是发往每个仓库给公司带来的边际效益不一样,而且每个仓库都 有一定的容量限制。现在公司必须决定往每个仓库发多少辆车,同时要 使整个公司的效益最高。 数据集work.expected_profit中包含了每个仓库每增加一台车所带来 的收益,warehouse_id代表了仓库代码,profit_margin代表每收到一台车 能带来的收益。数据集work.warehouse_constraint中包含了每个仓库的容 量限制。 图4.31中是work.expected_profit数据集中的部分数据和 work.warehouse_constraint数据集的全部数据。 图4.31 两个数据集部分内容 从数据集Work.Expected_profit中可以看出,对于不同的仓库来说, 增加一台车带来的效益是不一样的。例如,对于仓库VSC003和VSC002 来说,增加第一台车带来的边际效益分别为35.498和34.437;对于同一 个仓库,每增加一台车带来的边际效益也是不一样的,例如,对于仓库 VSC003来说,增加第一台车时带来的边际效益是35.498,在此基础上增 加第二台车带来的边际效益是31.611,符合边际效益递减规律。 因此在分配的时候,为了实现整个公司的效益最高,分配的原则就 是在仓库容量允许的情况下,将每台车都分配给边际效益最高的仓库。 以下SAS代码可以实现该目标。 data work.allocation; retain total_cars_before 1000; length warehouse_id $6; if _N_=1 then do; declare hash constraint(dataset: "work.warehouse_constraint"); constraint.definekey('warehouse_id'); constraint.definedata('capacity'); constraint.definedone(); call missing(warehouse_id,capacity); end; set work.expected_profit; rc = constraint.find(); if rc = 0 and capacity>=1 and total_cars_before>=1 then do; capacity=capacity-1; total_cars_after=total_cars_before-1; rc=constraint.replace(key:warehouse_id, data: capacity); if rc=0 then do; output work.allocation; total_cars_before=total_cars_before-1; end; else put "Error: Replace capacity failed!"; end; else if rc ne 0 then put "Error: Failed to find warehouse capacity!"; keep warehouse_id capacity profit_margin total_cars_before total_cars_after; run; 分配后的allocation数据集如图4.32所示。 图4.32 数据集work.allocation内容 其中: ·Total_car_before表示分配前的车辆,Total_cars_after表示每分配一 台车后所剩余的车辆。 ·Capacity表示仓库每收到一台车后的剩余容量。例如,在分配前车 辆总数为1000台,第一台车分配给VS003,剩余的车辆总数为999台, 仓库容量由原来的600降低为599。每分配一台车,系统都调 用.REPLACE更新HASH对象中仓库容量的数据。 本章小结 4.6 本章前半部分从PDV的角度,详细介绍了在DATA步中使用SET语 句和MERGE语句进行两个及多个数据集纵向串接和横向合并的原理, 并比较了使用SET语句和APPEND过程进行数据集纵向串接的异同。在 介绍数据集横向合并时,分别介绍了4种不同情形下DATA步的处理原 理,这4种不同的情形分别是:不使用BY语句的情形、BY变量值在所 有数据集中唯一的情形、BY变量值在某一数据集中存在重复的情形以 及BY变量值在多个数据集中存在重复的情形。 本章后半部分通过示例介绍了用于数据集更新和更改的两个语句: UPDATE语句和MODIFY语句,并补充介绍了数据集处理的一些小技 巧。 本章最后一小节简要介绍了HASH对象的处理原理。运用HASH对 象可以简便高效地实现数据集间比较复杂的操作,有兴趣的读者可以参 考SAS帮助文档进行更加深入的学习。 第5章 数据汇总与展现 在前面的章节中,介绍了如何将多个数据集中的数据整合到一个数 据集中,本章将在此基础上介绍如何利用SAS进行数据的汇总与展现。 在SAS中对数据进行汇总与展现时主要有两种形式:一类是列表,一类 是图形。 本章内容可以分为3部分,第一部分介绍通过列表形式进行数据汇 总与展现的PROC步,包括PRINT过程和TABULATE过程;第二部分介 绍SAS/GRAPH中常用的制作图形的PROC步,包括GPLOT过程和 GCHART过程;最后一部分介绍SAS中ODS输出控制的初步内容。 通过PRINT过程制作报表 5.1 PRINT过程是SAS中用于输出数据集内容的最简单常用的过程,它 可将选择的观测和字段以简单的矩形表格形式输出。在前面章节的示例 中,都是用它来显示对数据加工处理的结果的。默认设置下,SAS 9.3 及之后的版本中PRINT过程的结果会输出到HTML文件中。 5.1.1 制作简单报表 使用PRINT过程最简单的语法形式如下: PROC PRINT <DATA=数据集>; RUN; 当选项DATA=被省略时,系统默认输出最新创建的数据集。为了 保证代码的清晰易读,不建议省略。也可以使用数据文件地址和全称来 代替数据集。 例5.1:数据集ex.sales_quater1代表某公司2012年第一季度的销售数 据,Emp_ID代表员工ID,Emp_Name代表员工姓名,Year代表年份, Quarter代表季度,Sales代表销售数量,Price代表价格,Amount代表销 售额。现在要用PRINT过程输出它的内容。 示例代码如下: proc print data=ex.sales_quarter1; title 'Ex.sales_quanter1'; run; 输出内容如图5.1所示。 图5.1 例5.1打印输出数据集部分内容 图5.1是示例中的一部分输出结果,TITLE语句中指明了输出报表的 标题,最左侧的Obs代表观测在数据集中的序号,数据集中的所有变量 和观测都按观测序号依次输出。 1.使用选项OBS=修改观测序号列标签 在PRINT过程中可利用选项OBS=来修改最左侧观测序号一列的标 签。例如: proc print data=ex.sales_quarter1 obs='Observation Number'; title 'Ex.sales_quanter1'; run; 输出内容如图5.2所示。 图5.2 使用选项OBS=打印输出数据集部分内容 2.使用NOOBS选项不显示观测序号列 如果不需要在输出结果中显示观测序号列,则可以使用NOOBS选 项。例如: proc print data=ex.sales_quarter1 noobs; title 'Ex.sales_quanter1'; run; 输出内容如图5.3所示。 图5.3 使用NOOBS选项打印输出数据集部分内容 3.使用ID语句在输出中取代观测序号列 在有些数据集中,每条观测都有自己的关键字段或标识,例如员工 ID、姓名。因此在输出中,常希望用这些标识观测的变量代替观测序号 列置于输出的最左列。对于这种情况,可使用ID语句实现。ID语句的基 本语法如下: ID 变量1 < 变量2 变量3 …>; ID语句中的变量可以有多个,当有多个变量时,这些变量将按照它 们在ID语句中出现的顺序,依次置于输出的最左侧。 例5.2:在ex.sales_quarter1的输出中,用Emp_ID和Emp_Name代替 观测序号置于最左侧的列。 示例代码如下: proc print data=ex.sales_quarter1; title 'Ex.sales_quanter1 with ID'; id emp_id emp_name; run; 输出内容如图5.4所示。 图5.4 例5.2打印输出数据集部分内容 从图5.4可以看到,Obs列在输出中没有显示,取而代之的是 Emp_ID和Emp_Name位于左侧的列。 4.使用VAR选择输出的变量 在默认情况下,PRINT过程会输出数据集中的所有变量。为了选择 所要输出的变量以及输出变量的排列顺序,可以使用VAR语句。基本语 法如下: VAR 变量1 < 变量2 变量 3 …>; VAR语句中指明了需要输出的变量,以及在输出报表中这些变量从 左到右出现的顺序。 5.使用WHERE语句选择输出的观测 使用WHERE语句的基本语法如下: WHERE 表达式; WHERE语句的作用是只输出满足表达式条件的观测,表达式可以 是算术表达式或逻辑表达式。如果表达式是变量和字符的比较,则字符 必须用单引号或双引号括起来,并且区分大小写。 例5.3:从ex.sales_quarter1中输出Month=2的观测,并且在输出报表 中依次仅包含Emp_ID、Emp_Name、Month、Sales和Amount 5个变量。 示例代码如下: proc print data=ex.sales_quarter1 noobs; title 'Monthly Sales'; var emp_id emp_name month sales amount; where month=2; run; 输出内容如图5.5所示。 当使用WHERE语句时,SAS只会读取符合WHERE语句条件的观 测,这可以提高运行效率。为了实现对观测的选择,也可以使用数据集 选项WHERE=。上述代码等价于以下代码: proc print data=ex.sales_quarter1(where=( month=2)) noobs; title 'Monthly Sales'; var emp_id emp_name month sales amount; run; 6.使用数据集选项FIRSTOBS=和OBS= 当指定输出特定观测序号的观测时,可以使用数据集选项 FIRSTOBS=和OBS=。这两个选项可以单独使用,也可以联合使用。语 法如下: 数据集(FIRSTOBS= n1); 数据集(OBS= n2); 数据集(FIRSTOBS=n1 OBS=n2); 其中,选项FIRSTOBS=表示输出的第一条观测的序号为n1,选项 OBS=表示输出的最后一条观测的序号为n2。 ·单独使用选项FIRSTOBS=时,系统会输出序号从n1开始的所有观 测。 ·单独使用选项OBS=时,系统会输出前n2条观测。 ·联合使用时,系统会输出序号从n1开始到n2为止(包含n2)的观 测,所以要求选项FIRSTOBS>选项OBS,否则系统会报错。 例5.4:输出数据集ex.sales_quarter1中的第3~5条观测。 示例代码如下: proc print data=ex.sales_quarter1(firstobs=3 obs=5); title 'Observation: 3 to 5'; var emp_id emp_name month sales amount; run; 输出内容如图5.6所示。 图5.5 例5.3打印输出数据集内容 图5.6 例5.4打印输出数据集内容 输出中仅包含观测序号为3、4、5的观测。 5.1.2 制作增强型报表 在使用PRINT过程时,还可以使用SUM语句、BY语句和SUMBY语 句来对变量进行分组和汇总,增加报表输出的信息。 1.使用SUM语句对变量进行求和 在报表中除了可以显示数据集中的值,还可以使用SUM语句来进行 汇总和分组汇总。语法如下: SUM 变量1 < 变量2 变量3 … >; SUM语句中可以有多个变量,但是必须是数值型变量。 例5.5:接着例5.3输出数据集ex.sales_quarter1中Month=2的观测,并 计算2月的Sales总和及Amount总和。 proc print data=ex.sales_quarter1 noobs; title 'Monthly Sales for Total'; var emp_id emp_name month sales amount; where month=2; sum sales amount; run; 输出内容如图5.7所示。 图5.7 例5.5打印输出数据集内容 2.使用BY语句对变量进行分组汇总 例5.6:输出数据集ex.sales_quarter1中属于部门TSG的员工的观测, 并按员工、按月计算Sales的和及Amount的和,最后给出所有Sales的总 和及Amount的总和(属于部门TSG的员工的Emp_ID中第二个字符是 T)。 这时就需要在使用SUM语句的同时使用BY语句了,使用BY语句 后,SUM语句的功能就是按照BY变量分组,然后计算每个BY组合中指 定变量的合计,以及所有观测的总和。和上一章中讲的一样,在使用 BY语句时,数据集必须已经按照BY变量排过序。这里,数据集 ex.sales_quarter1并没有按照Emp_ID和Month排序,需要使用SORT过程 进行排序,并将排序后的数据集写出到数据集work.sales_quarter1中。示 例代码如下: proc sort data=ex.sales_quarter1 out=work.sales_quarter1; by Emp_ID Month; run; proc print data=work.sales_quarter1 noobs n='Sales Transactions' 'Total Sales Transactions'; title 'Emp Monthly Sales for Total'; var sales amount; where substr(emp_id,2,1)="T"; sum sales amount; by emp_id month; run; 输出内容如图5.8所示。 图5.8 例5.6打印输出内容 在上面的代码中,有以下两点需要注意: ·在BY语句中用了Emp_ID和Month两个变量,并相应地在VAR语句 中将这两个变量去除掉了,避免报表中这两个变量重复输出。 ·使用了选项N=来输出每个BY组合中的观测数以及整个数据集的观 测数,每个BY组合中的观测数以第一段文本Sales Transactions为标签, 输出在每个BY组合的最后一行,整个数据集的观测数以第二段文本 Total Sales Transactions为标签,输出在报表的最后一行。 在例5.6中,由于有两个BY变量,分别为Emp_ID和Month,所以输 出报表中对每个员工每月的Sales和Amount都做了汇总。如果只需要输 出每个员工的Sales及Amount的汇总数据,而不必将每个员工每个月的 汇总数据都输出,该如何修改上面的程序呢? 这时可以使用SUMBY语句控制汇总数据的输出,使用语法如下: BY 变量1 变量2 SUMBY 变量n; … 变量n 变量n+1 … 变量n+k; 其中,SUMBY语句中的变量n必须出现在BY语句中,PRINT过程 将对每个BY组合进行汇总,这里BY组合指的是使得变量1、变量2到变 量n取值完全相同的观测的集合,而不是使得变量1、变量2、…、变量 n、…、变量n+k取值完全相同的观测的集合。以下代码只实现了汇总每 个员工整个季度的Sales和Amount。 proc print data=work.sales_quarter1 noobs; title 'Emp Sales for Total'; var sales amount; where substr(emp_id,2,1)="T"; sum sales amount; by emp_id month; id emp_id month; sumby emp_id; run; 以上介绍了PRINT过程中的一些常用语句,这里总结如下: PROC PRINT DATA=数据集 <选项>; ID 变量1 < 变量2 …>; VAR 变量1 < 变量2 …>; WHERE 表达式; SUM 变量1 <变量2 …>; BY 变量1 <变量2 …>; SUMBY 变量1 <变量2 …>; RUN; 在PRINT过程中,各个语句的顺序可以任意改变。 5.1.3 改进报表显示 上面已经介绍了PRINT过程的基本用法,下面将进一步介绍如何使 用其他的语句和选项使输出的报表更加具有特色。主要包括以下几个方 面: ·添加标题和脚注 ·规定变量格式 ·规定变量标签 1.添加标题(TITLE)和脚注(FOOTNOTE) 在SAS所有的输出报表中,都可以添加标题和脚注。在前面章节 中,已经使用TITLE语句定义了输出报表的标题。这里详细讲解TITLE 语句和FOOTNOTE语句及其使用语法。它们的基本语法如下: TITLEn ‘标题内容’; FOOTNOTEn ‘脚注内容’; 其中: ·n的取值可以为1~10,TITLEn代表从上往下数第n级标题, FOOTNOTEn代表从上往下数第n级脚注,标题内容和脚注内容必须用 单引号或双引号括起来。 ·当n默认时,表示默认定义第1级标题或脚注。当n>m时,TILLEn 为TITLEm的下级标题,脚注同理。 ·当使用多个TITLE语句时,如果跳过某一级或某几级TITLEn没有 指定,则报表中对应的这一级或几级标题显示为空行。例如,指定了 TITLE1和TITLE3,但是并没有指定TITLE2时,输出报表中第一级标题 和第三级标题中间会有一行空行。 ·TITLE语句和FOOTNOTE语句是全局语句,可以出现在PRINT过 程前面或PRINT过程的程序中间,标题和脚注一经指定,在以后输出的 所有报表中都将被使用,直到被取消。 ·TITLEn语句的作用为指定新的标题内容和取消下级标题, FOOTNOTEn语句的作用为指定新的脚注内容和取消下级脚注。 例5.7:输出数据集ex.sales_quarter1的Month=2观测,在报表中汇总 2月的Sales和Amount,并为报表添加标题和脚注。 示例代码如下: proc print data=ex.sales_quarter1 noobs; title1 'Asia Pacific Acrea'; title3 'Monthly Sales Report'; footnote1 'February Sales Total'; footnote2 'COMPANY CONFIDENTIAL'; var emp_id emp_name month sales amount; where month=2; sum sales amount; run; 输出内容如图5.9所示。 图5.9 例5.7打印输出内容 从输出结果中可以看出两行标题中间有一空行,并且在报表下方输 出了两行脚注。 取消标题或脚注的语法如下: TITLEn; FOOTNOTEn; 其中: ·“TITLEn;”表示取消所有第m级标题,其中 m>=n。“FOOTNOTEn;”表示取消所有第m级脚注,其中m>=n。 ·“TITLE;”表示取消所有标题;“FOOTNOTE;”表示取消所有脚 注。 2.使用FORMAT语句规定输出格式 为了增加报表的可读性,可以在PRINT过程中加入FORMAT语句, 这可使变量在输出时采用指定的输出格式。其语法如下: FORMAT 变量1 <变量2 变量3 …> 输出格式1 <变量4 <变量5 …> 输出格式2 …> ; 其中,输出格式可以是SAS系统提供的输出格式,也可以是用户自 定义的输出格式。需要注意的是,在PROC步中使用FORMAT语句并不 会影响数据集中存储的数据,该输出格式仅对显示在报表中的变量起作 用。 表5.1中给出了SAS系统提供的常用数值型变量输出格式。 表5.1 SAS系统中常用数值型变量输出格式 在前面的章节讲过,日期型变量是SAS中一种重要数据类型,在 SAS内部,日期型变量是以数值的形式进行存储的,例如,day=19068 对应的日期为2012年3月16日;tm=32083对应的时间为上午8:54:43; event=1668138559对应的日期和时间为2012年11月10日上午3:49: 19。为了方便辨识,SAS提供了多种日期型变量的输出格式,表5.2给出 了一些常用的日期型变量输出格式。 表5.2 SAS系统中常用日期型变量输出格式 SAS系统提供的输出格式远不止以上这些,更多信息可以参考SAS 帮助文档。 注意 在使用日期型输出格式时,首先要明确数据集中存储的数 值代表日期、时间或者日期时间中的哪一种,然后再选用合适的输出格 式将其显示出来。 例5.8:输出数据集ex.sales_quarter1的观测,使得Amount采用更加 直观的输出格式。 示例代码如下: proc print data=ex.sales_quarter1 noobs; title 'Using Formating'; var emp_id emp_name month sales amount; where month=2; format amount dollar14.2; run; 输出内容如图5.10所示。 图5.10 例5.8打印输出内容 Amount变量使用了DOLLAR14.2的输出格式,其中14代表可以输出 的最大宽度为14个字符,2代表小数点后预留了两位,这14个字符中包 含一个DOLLAR符号、一个小数点,以及小数点后两位,整数部分则从 右到左每3位中间加一个逗号。 注意 在上例中使用FORMAT语句设置数值型变量的输出格式 时,要注意使输出格式中指定的最大宽度能够适用在该变量的最大值 上,其中DOLLOR符号($)、小数点和逗号分别占1个字符。 3.使用LABEL语句规定输出变量的标签 默认情况下,在PRINT过程的输出中,系统会使用数据集中的变量 名作为报表的表头,有时为了使得报表更为用户化,可以使用变量的标 签来代替变量名。变量的标签可以在创建数据集时定义。如果数据集的 变量已经设定了标签,那么只要在PROC步中加上选项LABEL,系统就 会自动采用变量的标签代替变量名。 如果数据集中的变量没有定义标签,或者在报表中需要使用不同于 数据集中已有标签的形式,则可以在PRINT过程中使用LABEL语句,定 义新的标签。这时再使用选项LABEL就可以在报表中显示新定义的标 签了。 LABEL语句的使用语法如下: LABLE 变量1=’标签文本’ 变量2=’标签2’ …; 标签文本必须使用单引号或双引号括起来,长度不得超过256个字 符,包括空格在内。 例5.9:修改例5.8的程序,为Emp_ID、Emp_Name、Sales和Amount 定义标签。 示例代码如下: proc print data=ex.sales_quarter1 label; title 'Using Formating and Label'; var emp_id emp_name month sales amount; where month=2; format amount dollar14.2; label emp_id='员工工号' emp_name='员工姓名' Sales='销售数' Amount='销售金额'; run; 输出内容如图5.11所示。 (1)分行显示标签 有时标签可能过长,系统会自动根据列宽将标签分成几行显示,但 是系统分隔标签时不会考虑具体含义,为了控制标签的分行,可以使用 选项SPLIT=。使用选项SPLIT=时,需要进行以下两个操作。 ·在PROC PRINT语句中使用选项SPLIT=指明分隔符,使用选项 SPLIT=的基本语法如下: SPLIT='分隔符' ·在LABEL语句中定义含有分隔符的标签。 分隔符可以是字母、数字或特殊符号中的任何一种,且只占一个字 符,同时必须使用单引号或双引号括起来。在使用选项SPLIT=时,不 需要使用选项LABLE,因为选项SPLIT=已经隐含了PRINT过程将使用 标签。 (2)在报表中添加空行 在PROC PRINT语句中还可以使用选项BLANKLINE=在报表中添加 空行。使用方法如下: BLANKLINE=n; 在输出报表中,每输出n行数据后输出一行空行。 例5.10:在PROC PRINT语句中定义斜杠(/)为分隔符,并在 LABEL语句中将分隔符加入标签定义中,同时使得报表的每行数据后 有一个空行。 示例代码如下: proc print data=ex.sales_quarter1 split='/' blankline=1; title 'Using Split and Adding blank line'; var emp_id emp_name month sales amount; where month=2; format amount dollar14.2; label emp_id='员工/工号' emp_name='员工/姓名' Sales='销售数' Amount='销售/金额'; run; 输出内容如图5.12所示。 图5.11 例5.9打印输出内容 图5.12 例5.10打印输出内容 本节介绍了PRINT过程的常用方法和选项,SAS提供了丰富的选项 供用户制作更加个性化的报表,读者可以查阅SAS帮助文档。 通过TABULATE过程制作汇总报表 5.2 在上一节中介绍了使用PRINT过程制作详细的报表,本节将介绍如 何使用TABULATE过程制作各种格式的汇总报表,在这些报表中将不 再显示各条观测的内容,而是分类汇总的结果。对于观测数庞大的数据 集,通过汇总报表可以更集中地反映数据的概要特征。 5.2.1 制作基本汇总报表 TABULATE过程不同于PRINT过程,在不使用其他语句的情况下, PRINT过程会输出数据集的详细内容作为报表,但是使用TABULATE过 程必须编写程序进行报表布局。 TABULATE过程的基本语法如下: PROC TABULATE DATA=数据集 <选项>; CLASS 变量1 <变量2 变量3 …>; VAR 变量4 <变量5 变量6 …>; TABLE <<页表达式,> 行表达式,> 列表达式</选项>; RUN; 其中: ·CLASS语句中的变量称为分类变量,依据分类变量的不同取值可 以将数据集中的观测划分为不同的分组类别。针对这些分组类别, PROC TABULATE分别计算分析变量(后面会讲到)的统计量。数值型 和字符型的变量都可以作为分类变量,但通常类别(分类变量的取值个 数)不宜太多。 ·VAR语句中的变量称为分析变量,这些变量的值就是进行分析计 算的对象。只有数值型变量才可以作为分析变量。为了使汇总报表有意 义,通常要求分析变量的求和或平均等计算是有实际含义的。 ·TABLE语句是用来定义表格布局的,通过TABLE语句可以定义汇 总表格的列、行和页3个维度的布局。在一个TABLE语句中列表达式是 必须的,其余表达式可以省略,但是顺序必须依次是页表达式、行表达 式、列表达式,各表达式中间用逗号(,)分隔。每个维度的表达式由 操作符和元素组成。操作符包括空格操作符和星号(*)操作符,元素 包括分析变量、分类变量、通用的分类变量ALL、统计量、输出格式和 标签等。 下面将具体介绍如何使用操作符和元素。 以下程序是使用TABLE语句的简单示例,在定义一个三维的汇总 表格后,系统将根据Dept的取值把汇总表格分页,以Emp_Name的取值 作为行,以Amount的取值作为列。代码如下: table Dept, Emp_Name, Amount; 一个TABULATE过程中可以有多个CLASS语句、VAR语句和 TABLE语句。 注意 1)在TABULATE过程中,任何一个变量都只能是分类变 量或分析变量,但不可以既作为分类变量又作为分析变量。 2)TABLE语句中出现的任何变量都必须在CLASS语句或者VAR语 句中出现过。 3)所有分析变量必须出现在一个维度(列、行或者页)中,也就 是说,仅有一个维度的表达式中包含分析变量。 在使用TABULATE过程制作报表前,必须根据所要显示的内容明 确以下4个问题: ·什么变量作为分类变量。 ·什么变量作为分析变量。 ·需要计算什么统计量。 ·用什么样的表格展示结果。 接下来将按照以上的思路结合例子来介绍如何使用TABULATE过 程制作报表。 在本节中将使用数据集ex.sales_halfyear作为示例数据,该数据集中 含有以下字段:员工工号(Emp_ID)、员工姓名(Emp_Name)、州 (State)、销售月份(Month)、销售数量(Sales)、销售价格 (Price)、产品类型(Type)、Amount(销售金额)。部分数据如图 5.13所示。 图5.13 数据集ex.sales_hafyear部分内容 例5.11:使用TABULATE过程制作一个简单表格,显示每个州的交 易次数。 分析:这里的分类变量为State,交易次数是频数统计量,那么分类 变量的默认统计量就是频数统计量。 以下代码可以实现上述功能: proc tabulate data=ex.sales_halfyear; title1 'Sales in North America'; title2 'Trancations in Each State'; class state; table state; run; 输出内容如图5.14所示。 在这段程序中,使用了CLASS语句和TABLE语句,没有使用VAR 语句。 ·CLASS语句中定义了一个分类变量state,当TABLE语句使用变量 state时,state的每个取值将成为一个分组类别。 ·如果TABLE语句中只指定了一个表达式state,则这个表达式作为 列表达式,state的每个取值将在表格中作为一列。 ·当没有定义分析变量时,在表格中显示的统计量默认为频数统计 量N。 ·TITLE语句是全局语句,这里定义两级标题,可以参考PRINT过程 中的介绍。 1.分类变量中含有缺失值 如果分类变量中含有缺失值,在汇总时系统会自动删除分组变量为 缺失值的观测。在PROC TABULATE中使用选项MISSING,可以将缺 失值作为一个特定的类别在报表中显示出来。使用方法如下: PROC TABULATE DATA=数据集 MISSING; 当选项MISSING使用在PROC TABULATE语句中时,会对所有 CLASS语句中的分类变量都起作用。第一次对某个数据集使用 TABULATE过程时,最好能先在PROC TABULATE语句中使用选项 MISSING,这样能对数据集中的各变量是否存在缺失值有比较全面的了 解。 在制作报表过程中,如果只需要将特定变量的缺失值在报表中显 示,可以在CLASS语句中使用选项MISSING。语法如下: CLASS 变量1 <变量2 … > / MISSING; 这时选项MISSING只对该CLASS语句中的变量起作用,如果 TABULATE过程中还有其他CLASS语句,选项MISSING对其他变量不 起作用。 修改例5.11的代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales in North America'; title2 'Trancations in Each State'; class state/missing; table state; run; 输出如图5.15所示。 图5.14 图5.15 例5.11打印输出内容 State含有缺失值的输出 例5.12:使用TABULATE过程制作一个简单表格,显示每个州的交 易金额。 分析:这里的分类变量是State,分析变量是Amount,使用求和统 计量,在设计表格的时候,可以将State的取值设为行,交易金额的和设 为列,这样就不难写出代码了。代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales in North America'; title2 'Total Amount Sold in Each State'; class state; var amount; table state,amount; run; 输出内容如图5.16所示。 图5.16 例5.12打印输出内容 在TABLE语句中,分类变量State定义为行维度,State的每个取值 在报表中为一行;分析变量amount定义为列维度,这里没有为amount指 定特定的统计量,系统使用默认的SUM统计量。 例5.13:使用TABULATE过程制作一个简单表格,显示每种产品在 每个州的交易金额,并将不同的产品分页展示。 分析:这里的分类变量有两个,分别是State和Type,分析变量是 Amount,使用求和统计量,这里需要使用Type来分页,将State取值设 为行,交易金额的和设为列。示例代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales in North America'; title2 'Total Amount Sold in Each State for Each Type'; class state type; var amount; table type,state,amount; run; 输出如图5.17所示。 图5.17 例5.13打印输出内容 以上汇总表格中每种产品分属不同的页,每页中显示每个州的交易 金额总和,列的名称包含了分析变量Amount和统计量SUM。 5.2.2 制作高级汇总报表 在上面的介绍中,TABLE语句的每个维度都只有一个分类变量或 分析变量。事实上,在TABLE语句中可以使用更为复杂的维度表达式 来制作汇总报表。 1.使用空格操作符制作连排表格 在TABLE语句的表达式中,使用空格操作符隔开两个元素可以制 作连排表格。 例5.14:使用TABULATE过程制作一个汇总表格,显示每个州的交 易数量及每个月的交易数量。 分析:这里的分类变量为State和Month,使用频数统计量,可以考 虑制作连排表格。示例代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales in North America'; title2 'Total Transactions'; class state month; table state month; run; 在上述代码中,TABLE语句中有两个变量:state和month,用空格 隔开,共同组成了列表达式。 输出内容如图5.18所示。 图5.18 例5.14打印输出内容 在输出结果中,state和month的取值共同构成了汇总表格的列。 2.使用星号(*)操作符制作交叉组合表格 在TABLE语句中,可使用星号(*)操作符隔开两个元素制作交叉 组合表格。 例5.15:使用TABULATE过程制作一个汇总表格,显示每种产品在 每个州的销售金额总和。 分析:分类变量为State和Type,分析变量为Amount,使用求和统 计量。示例代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales in North America'; title2 'Total Amount Sold For Each Type In Each State'; class state type; var amount; table type*state,amount; run; 表达式type*state表示行维度,type在星号(*)操作符的左边,因 此在汇总表格中type位于state的左边。 输出内容如图5.19所示。 图5.19 例5.15打印输出内容 3.使用关键字ALL 关键字ALL可以使用在TABLE语句的页、行、列表达式中,它可以 作用在一个或者多个分类变量上。ALL可以理解为一个特殊的分组类 别,这个类别中包含所作用变量的所有分组类别内的全部观测。 例5.16:使用关键字ALL修改例5.15中的程序,显示每种产品在每 个州的销售总和及所有产品在所有州的销售总和。 示例代码如下: proc tabulate data=ex.sales_halfyear; title 'Type*State All'; class state type; var amount; table type*state all,amount; run; proc tabulate data=ex.sales_halfyear; class state type; var amount; title 'Type*(State All)'; table type*(state all),amount; run; 输出内容如图5.20所示。 图5.20 例5.16打印输出内容 为了介绍关键字ALL,这里分别制作了两个汇总表格。 ·左边的表格:关键字ALL作用在type*state之后,ALL相当于和 type*state一样的一个类别,最后一行的类别显示为ALL(全部),表示 计算所有产品在所有州的销售总和。 ·右边的表格:关键字ALL只作用在state上,相当于是state的一个特 殊类别,所以在每个State的所有类别之后,都有一行显示为ALL(全 部),用于计算某种产品在所有州的销售总和。 4.统计量 在上面的例子中,使用的都是系统默认的统计量,其中分类变量的 默认统计量是频数统计量N,分析变量的默认统计量是求和统计量 SUM。除了频数统计量和求和统计量,TABULATE中还可以计算许多 其他统计量。表5.3中是部分较为常见的统计量的关键字。 表5.3 注意 常见统计量关键字 前面介绍过,在一个TABLE语句中,所有分析变量必须 出现在一个维度中,也就是说,只有在一个维度的表达式中才会包含分 析变量。同样的,所有的统计量也必须出现在一个维度中,但是分析变 量和统计量可以分别出现在不同的维度中。 例5.17:使用TABULATE过程制作一个汇总表格,显示每种产品在 每个州的交易次数、平均销售金额、销售金额总和。 分析:分类变量为Type和State,分析变量为Amount,统计量为频 数(N)、平均值(MEAN)和求和统计量(SUM)。示例代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales Report in North America'; class state type; var Amount; table type*state all,amount*n amount*mean amount; run; 输出内容如图5.21所示。 图5.21 例5.17打印输出内容 这里使用了多个统计量N、MEAN和SUM。表达式amount*n amount*mean amount可以使用括号操作符写成amount*(n mean sum)。 5.2.3 改进报表显示 上面已经介绍了TABULATE过程的用法,下面将进一步介绍如何 使用其他的语句、选项及关键字,使得输出的报表更为用户化,且具有 可读性。主要包括以下几个方面: ·添加标题和脚注 ·规定变量或统计量标签 ·控制分类变量输出类别 ·控制汇总数据的输出格式 在TABULATE过程中,标题和标注的使用方法和在PRINT过程中类 似,这里不赘述。 1.规定变量或统计量标签 在使用TABULATE过程制作汇总表格时,若数据集中已经为变量 设立了标签,则在汇总表格中,行或列的表头中将显示其标签而非变量 名。如果数据集中的变量没有定义标签,或者在报表中需使用不同于数 据集中已有标签的形式,则可以使用以下两种方法定义变量的标签。 ·使用LABEL语句定义标签,使用语法和PRINT过程中类似。 ·在TABLE语句中使用以下语法改写变量名: 变量='标签' 其中变量包括通用分类变量ALL。如果不需要某个变量的名称出现 在汇总表格中,则可以将这里的'标签'置为''。 例5.18:将汇总表格中的变量都使用中文标签显示。 示例代码如下: proc tabulate data=ex.sales_halfyear; title1 'Sales Report in North America'; class state type; var amount; table type='产品类型'*state all='汇总',amount=''*(n mean sum); label state='州'; run; 输出内容如图5.22所示。 图5.22 例5.18打印输出内容 若要对汇总表格中的统计量名称进行更改,类似于变量标签的设 定,可以使用以下两种方法中的任意一种实现。 ·使用KEYLABEL语句定义标签,相关语法如下: KEYLABEL 统计量关键字1='统计量标签1' 统计量关键字2='统计量标签2'; ·在TABLE语句中使用以下语法改写变量名: 统计量关键字='统计量标签' 例5.19:接着例5.18,将汇总表格中的统计量改为中文名称。 示例代码如下: proc tabulate data=ex.sales_halfyear; title1 '北美销售概况'; title2 '产品类型和季度角度分析'; class state type; var amount; table type='产品类型'*state all='汇总',amount=''*(n='销售次数' mean='平均销售 金额' sum='销售金额之和'); label state='州'; run; 输出内容如图5.23所示。 图5.23 例5.19打印输出内容 在上述程序中,实现了将统计量N、MEAN及SUM分别用中文标签 显示。 2.使用FORMAT语句控制分类变量的类别 在TABULATE过程中,可以使用FORMAT语句控制分类变量的类 别。使用语法如下: FORMAT 变量1 <变量2 …> 输出格式1 <变量3 输出格式2…>; 对分类变量使用FORMAT语句后,将按格式化后的值进行分类,同 时,在汇总表格中显示的类别也是变量格式化后的取值。 例5.20:显示使用TABULATE过程制作一个汇总表格,显示每种产 品每个季度的销售次数、平均销售金额、销售金额总和。 在数据集ex.sales_halfyear中没有季度字段,可以对变量month使用 用户自定义格式$quarter。 示例代码如下: proc format; value quarter 1-3='季度1' 4-6='季度2' 7-9='季度3' 10-12='季度4'; run; proc tabulate data=ex.sales_halfyear; title1 '北美销售概况'; title2 '产品类型和季度角度分析'; class type month ; var amount; table type='产品类型'*(month all='半年汇总') all='汇总' ,amount=''*(n mean= '平均销售金额' sum='销售金额之和'); format month quarter.; keylabel n='销售次数'; run; 输出内容如图5.24所示。 图5.24 例5.20打印输出内容 这里运用FORMAT过程定义了输出格式quarter,将month=1、2、3 归为季度1,month=4、5、6归为季度2,所以在输出报表中,我们看到 month的取值变成季度1和季度2(数据集中只有上半年的数据),并且 分类汇总也是在此基础上进行的。 接下来,将对FORMAT过程进行简要介绍。 SAS系统提供的FORMAT过程专门供用户自定义输入与输出格式。 通过FORMAT过程,用户可以自己设定输出格式,对变量的不同值或者 不同范围的值可设定不同的“标签”来显示,从而增加数据集和报表的可 读性。涉及的语法如下: PROC FORMAT; VALUE 格式名称 范围1='标签1' 范围2='标签2' …; RUN; 其中,格式名称是长度不超过8位的SAS变量名,但是不能以数字 结尾,也不能和系统已有的输出格式重名。若为字符型的输出格式,格 式名称必须以DOLLAR符号($)开始,包括DOLLAR符号在内不能超 过8个字符。 以下程序定义了两个输出格式。 proc format; value $gender 'M'='Male' 'F'='Female' Other='Wrong Code'; run; proc format; value $grade Low-59='Under Grade' 60-80='Average' 81-90='Good' 91-High='Excellent'; run; 在上述程序中,OTHER表示除了列举范围以外的所有值,包含缺 失值,一般使用在字符型输出格式中;LOW表示最小的数值,包含缺 失值;HIGH表示最大的数值。 用户自定义的输出格式和系统自带的输出格式使用方法一样。 如果没有指定逻辑库,则FORMAT过程定义的输出格式保存在 WORK逻辑库中FORMATS目录下,如图5.25所示。一经定义后,即可 一直调用,但是由于是保存在临时库中的,因此一旦关闭SAS会话,它 就会被删除。 图5.25 WORK逻辑库中FORMATS目录 为了使得定义的FORMAT能够保存下来,可在以后的SAS会话中被 调用,可以在定义FORMAT的时候如下指定逻辑库: PROC FORMAT LIBRARY=逻辑库名; 这样一来,定义好的FORMAT就会被保存到该逻辑库中的 FORMATS目录下。选项LIBRARY=缺省时,FORMAT会被默认保存到 WORK逻辑库中的FORMATS目录下。 为了方便用户在以后的SAS会话中直接调用已经定义的FORMAT, SAS提供了一个系统选项FMTSEARCH,用于搜索指定的逻辑库及目录 下面的FORMAT。选项FMTSEARCH的使用方法如下: OPTIONS FMTSEARCH=(逻辑库名1 <逻辑库名2 … >); 例如: Options fmtseatch=(abc xyz); 在上面的例子中,系统会首先搜索WORK.FORMATS,然后再依次 搜索ABC.FORMATS和XYZ.FORMATS,直到找到需要的FORMAT。 如果用户希望系统优先使用ABC逻辑库中的FORMAT,可以在 OPTIONS语句中如下设置: options fmtseach=(abc work xyz); 如此一来,系统就会依次搜索ABC.FORMATS、 WORK.FORMATS、XYZ.FORMATS,直到找到需要的FORMAT。 关于FORMAT过程更加详细的介绍及用法请参考SAS帮助文档。 3.控制汇总数据的输出格式 在TABULATE过程中,有以下两种方法可以控制汇总数据的输出 格式: ·在TABLE语句的变量(或变量标签)或统计量(或统计量标签) 后面可使用下面语法控制行或列的输出格式。 *F=输出格式 ·在PROC TABULATE语句中使用选项FORMAT=,这时指定的格式 将作为所有汇总数据的输出格式,除非某些行或某些列指定了特定的输 出格式。 例5.21:将例5.20中的汇总数据的销售额用输出格式dollar12.2显 示。 示例代码如下: proc tabulate data=ex.sales_halfyear; title1 '北美销售概况'; title2 '产品类型和季度角度分析'; class type month; var amount; table type='产品类型'*(month all='半年汇总') all='汇总',amount=''*(n='销售次数' mean='平均销售金额'*f=dollar12.2 sum='销售金额之和' *f=dollar12.2); format month quarter.; run; 输出内容如图5.26所示。 图5.26 例5.21打印输出内容 上述程序中,用“*F=输出格式”分别为平均销售金额和销售金额之 和的汇总数据设定了输出格式。 5.3 通过GPLOT过程制作图形 和数据报表一样,图形也是展现数据的重要方法,图形的直观效果 是数据报表无法替代的。SAS/GRAPH是SAS进行数据可视化展现的重 要组成部分,具有强大的作图功能。可以展现的图形包括以下这些: ·散点图与连线图(PLOTS) ·图表(CHARTS) ·地图(MAPS) ·三维图(3D GRAPHICS) ·幻灯片(TXET SLIDES) 本章中主要介绍SAS/GRAPH中两个基本的作图过程:作图过程 (GPLOT)和图表过程(GCHART)。读者有兴趣可以参考SAS帮助文 档学习更多的作图过程。 使用GPLOT过程可以制作平面的散点图和连线图。平面的散点图 就是以数据集中某两个变量作为纵坐标和横坐标,以每个观测为一个数 据点,数据集中的多个观测就形成一幅散点图,连线图就是将分散的数 据点用直线或者曲线连接起来。散点图和连线图在分析比较数据的趋势 时比较常用。 5.3.1 制作散点图 使用GPLOT过程制作散点图的基本语法如下: PROC GPLOT DATA=数据集; PLOT 纵坐标变量*横坐标变量; RUN; PLOT语句; … RUN; QUIT; 一个GPLOT过程中可以使用多个RUN语句,并以QUIT语句结尾。 每个RUN语句中也可以使用多个PLOT语句。 ·在一个PROC语句中使用多个RUN语句,可以针对不同的图形使用 不同的WHERE语句,利用全局语句定义不同的标题、脚注、坐标轴等 属性。在SAS/GRAPH中GPLOT、GCHART、GMAP和GSLIDE支持使 用多个RUN语句。 ·QUIT语句表示结束该PROC步。如果在RUN语句后面出现其他的 PROC步或者DATA步,该PROC步也会自动结束。 例5.22:数据集ex.sales_year中包含了某公司自1998年至今在北美和 欧洲的销售数据(交易数量和销售金额)。其中变量如下:年份 (Year)、N_Transactions(北美的交易数量)、N_Amount(北美的销 售金额)、E_Transactions(欧洲的交易数量)、E_Amount(欧洲的销 售金额)。要求制作一个散点图,显示每年的北美销售金额。 示例代码如下: proc gplot data=ex.sales_year; title 'Yearly Amount in North America'; plot N_Amount*Year; run; quit; 输出内容如图5.27所示。 这是最简单的散点图,横坐标为Year,纵坐标为N_Amount,图形 的元素都使用系统的默认设置,每个数据在图上显示为一个“+”号。 1.选项RESET= 由于图形的展现涉及的方面很多,包括颜色、线型、字体等,因此 图形选项也很多,在制作图形时可以使用GOPTIONS语句来控制各种选 项。在开始学习制作图形时,不妨使用选项RESET=ALL,它可使所有 的图形选项都恢复系统默认的设置。需要注意的是,在使用该选项之 后,TITLE语句与FOOTNOTE语句中指定的标题和脚注也将被取消。 2.使用TITLE和FOOTNOTE语句设置标题和脚注 和前面制作报表一样,在作图时也可以使用TITLE语句和 FOOTNOTE语句在图形上加入标题和脚注。需要注意的是,在 SAS/GRAPH中,TITLE语句和FOOTNOTE语句除了可以设定标题和脚 注的内容以外,还可以对其字体、大小、颜色进行设定。 在制作图形时,TITLE语句和FOOTNOTE语句的使用形式如下: TITLEn 选项 '标题内容'; FOOTNOTEn 选项 '脚注内容'; 其中n=1~10,缺省时,n默认取值为1。TITLEn和FOOTNOTEn的更 新和置换规则和5.2.3节中介绍的一样。 图5.27 例5.22打印输出图形 将所有TITLE语句和FOOTNOTE语句恢复默认设置的方法有以下两 种: ·GOPTIONS RESET=(TITLE FOOTNOTE); ·GOPTIONS RESET=GLOBAL; 表5.4是TITLE语句和FOOTNOTE语句中常用的选项。 表5.4 TITLE语句和FOOTNOTE语句的常用选项 3.使用SYMBOL语句设置散点属性 在图5.27中,如果希望输出的数据在图上显示为一个红色的圆点, 该如何进行设置呢?这时需要使用SYMBOL语句。在GPLOT过程中, SYMBOL语句用来设置散点的符号、颜色等属性。使用形式如下: SYMBOLn 选项; 其中,n是不同SYMBOL的序号,取值为1~255,缺省时默认取值 为1。SYMBOL语句和TITLE语句一样是全局语句,可以出现在PROC步 前面或PROC步的程序中间。 将某个SYMBOL语句恢复为默认设置的方法如下: SYMBOLn; 将所有SYMBOL语句都恢复为默认设置的方法如下: GOPTIONS RESET=SYMBOL; 例5.23:在例5.22的散点图中,将散点符号换成红色的“.”。 示例代码如下: symbol value=dot cv=red; proc gplot data=ex.sales_year; title f='Albany Amt' c=blue h=3 u=2 'Yearly Amount in North America'; footnote j=r 'Optimization Solution Co. Ltd'; plot N_Amount*Year; run; quit; goptions reset=all; 输出内容如图5.28所示。 图5.28 例5.23打印输出图形 以上程序使用SYMBOL语句通过选项VALUE=将散点符号设置为圆 点(.),通过选项CV=将散点颜色设置为红色。并且利用TITLE语句和 FOOTNOTE语句设置了标题和脚注的文字和属性。 可在SYMBOL语句中用不同的选项来设置不同的属性,如表5.5所 示是SYMBOL语句中用于设置散点属性的选项。 表5.5 SYMBOL语句常用散点属性选项 5.3.2 制作连线图 在SYMBOL语句可以使用选项INTERPOL=制作连线图。语法如 下: SYMBOLn INTERPOL=插值方法; 选项INTERROL=可以简写为I=。其中插值方法主要有以下几种。 ·NONE:表示不作连线图,这是系统默认取值。 ·JOIN:表示将数据点按照数据集中出现的顺序用直线连接。 ·SPLINE:表示使用光滑的插值曲线将数据点按照数据集中出现的 顺序连接起来,并且使得曲线经过每个数据点。 ·SMnn:表示用光滑的插值曲线来拟合数据点,曲线可以不经过数 据点,nn的取值为0~99,取值越大,插值曲线的光滑程度越高。 ·Rxyzzzmm:表示根据数据点作回归线,x表示回归类型,取值为 L、Q、C;y表示回归线是否过数据点,取值为0、1;zzz表示置信限, 取值为CLM、CLI;mm表示置信水平。 ·NEEDLE:表示针对每个点画一条从点到横坐标轴的连线。 ·STEPxyz:表示制作阶梯图,x表示数据点在阶梯上的位置,取值 为L、R、C;y表示是否用竖线连接各阶梯;z表示是否对数据按横坐标 变量排序。 同时,SYMBOL语句中还提供了更多的选项来控制连线线型、粗 细、颜色等属性。如表5.6所示是SYMBOL语句中常用的设置连线属性 的选项。 表5.6 SYMBOL语句常用连线属性选项 下面将例5.23中图形的散点用蓝色的实线连接起来。示例代码如 下: symbol value=dot cv=red interpol=join ci=blue; proc gplot data=ex.sales_year; title 'Yearly Amount in North America'; plot N_Amount*Year; run; quit; goptions reset=all; 输出内容如图5.29所示。 图5.29 例5.23打印输出用实线连接散点的图形 当定义了多个SYMBOL语句时,可以使用以下语法来指定图形使用 特定的SYMBOL语句设置的属性: PLOT纵坐标变量*横坐标变量=n; 该语句表示该散点图使用SYMBOLn设置的属性。 1.使用选项HAXIS=和VAXIS=控制坐标轴取值范围 在图5.29中,可观察到横坐标的取值范围为1990~2020。其实我们 的数据集中年份截止到2012年。如果想使得图形中横坐标的取值范围为 1990~2012,该如何设置呢?作图语句中提供了选项HAXIS=和选项 VAXIS=,可以用来分别设定横坐标和纵坐标的取值范围。如: symbol value=dot cv=red interpol=join ci=blue; proc gplot data=ex.sales_year; title 'Yearly Amount in North America'; plot N_Amount*Year /haxis=1990 to 2012 by 5; run; quit; goptions reset=all; 除了这样直接定义坐标轴的取值范围以外,还可以通过AXIS语句 来进行这一操作。AXIS语句除了可以设定坐标轴范围,还可以对坐标 轴进行更加丰富的设置。 2.使用AXIS语句设置坐标轴属性 通过AXIS语句可以设定坐标轴的刻度范围、颜色、描述标签、每 两个主刻度中间次刻度的个数等属性。和SYMBOL语句一样,AXIS语 句也是全局语句,它也可以用于其他作图过程中坐标轴的设置。AXIS 语句的形式如下: AXISn 选项; 其中,n是不同AXIS的序号,取值为1~99,缺省时默认取值为1。 SYMBOL语句和TITLE语句一样是全局语句,可以出现在PROC步前面 或PROC步的程序中间。 将某个AXIS语句恢复为默认设置的方法如下: AXISn; 将所有AXIS语句都恢复为默认设置的方法如下: GOPTIONS RESET=AXIS; 在GPLOT过程中使用AXIS语句的形式如下: PROC GPLOT DATA=数据集; PLOT 纵坐标变量*横坐标变量/ HAXIS=AXISn VAXIS=AXISm; AXISn 选项; RUN; QUIT; 其中,HAXIS=AXISn指明纵坐标用AXISn语句设定的属性, VAXIS=AXISm指明横坐标用AXISm语句设定的属性,AXISn语句可以 出现在PROC步前面或者PROC步中间。 例5.24:在以下的连线图中对横坐标和纵坐标的主刻度和次刻度进 行设定。 示例代码如下: axis1 axis2 order=(1990 to 2012 by 5) ; order=(13000 to 20000 by 1000) minor=(color=blue height=0.25 number=1); symbol value=dot cv=red interpol=join ci=blue; proc gplot data=ex.sales_year; title 'Yearly Amount in North America'; plot N_Amount*Year/haxis=axis1 vaxis=axis2; run; quit; goptions reset=all; 输出内容如图5.30所示。 图5.30 例5.24打印输出图形 在上面的程序中,先通过AXIS语句设置了AXIS1和AXIS2,在 AXIS1中规定坐标轴的取值为1990~2012,每5个点一个主刻度,每个主 刻度中间有一个次刻度,次刻度显示为蓝色,同理还定义了AXIS2。在 PLOT语句中,使用选项HAXIS=AXIS1和VAXIS=AXIS2分别指定了横 坐标和纵坐标调用AXIS1和AXIS2设置的属性。 AXIS语句中的选项很多,用法也非常丰富,这里简单罗列了一些 常用的选项及用法示例,如表5.7所示。读者如需要深入学习AXIS语句 的使用,可以参考SAS帮助文档。 表5.7 5.3.3 AXIS语句常用选项 制作多幅图形 前面提到在一个GPLOT过程中可以使用多个PLOT语句,这时每个 语句都可以制作一幅单独的图形。其实,使用一个PLOT语句也可以制 作多幅图形。使用语法如下: PLOT 纵坐标变量1*横坐标变量1 纵坐标变量2*横坐标变量2 < … / 选项>; 例5.25:绘制北美销售金额连线图和欧洲销售金额连线图。 示例代码如下: axis1 axis2 order=(1990 to 2012 by 5) minor=(color=blue number=1); order=(13000 to 20000 by 1000) minor=(color=blue height=0.25 number=1); symbol value=dot cv=red interpol=join ci=blue; proc gplot data=ex.sales_year; title 'Yearly Amount Series'; plot N_Amount*Year E_Amount*Year/haxis=axis1 vaxis=axis2; run; quit; goptions reset=all; 输出内容如图5.31所示。 上述程序制作了两幅图,第一幅是由PLOT语句中的 N_Amount*Year制作的,第二幅是由E_Amount*Year制作的,两幅图上 横纵坐标的尺度都相同。 为了进行比较,有时需要将多条连线绘制在同一幅图形中。例如, 为了比较北美和欧洲的销售及变化趋势,除了上面的做法,更方便的是 将北美和欧洲的销售连线图画在同一幅图形中。又如在进行时间序列分 析时,为了比较预测值和实际值的趋势及大小,也需要在同一幅图中比 较。 图5.31 例5.25打印输出图形 在同一幅图形中绘制多条连线有以下3种方法: ·使用选项OVERLAY叠加图形。 ·使用PLOT2语句叠加图形。 ·使用分组变量制作多条连线图。 下面将具体介绍每种方法及其使用场景,读者可以根据具体的数据 集选用不同的方法。 1.使用选项OVERLAY叠加图形 使用选项OVERLAY可以将同一个PLOT语句中的多个图形展现在 同一幅图形中。 例5.26:将例5.25中的两条连线绘制在同一图形中。 示例代码如下: axis1 order=(1990 to 2012 by 5) minor=(color=blue number=1); axis2 order=(13000 to 20000 by 1000) minor=(color=blue height=0.25 number=1); symbol1 value=dot cv=red interpol=join ci=red; symbol2 value=# cv=green interpol=join ci=green line=4; proc gplot data=ex.sales_year; title 'Yearly Amount Series'; plot N_Amount*Year E_Amount*Year/overlay legend haxis=axis1 vaxis=axis2; run; quit; goptions reset=all; 输出内容如图5.32所示。 由于两个连线在同一图形中,系统约定由SYMBOL1语句设定第一 条连线,SYMBOL2语句设定第二条连线。所以当有多条连线时,应该 以不同序号的SYMBOL语句分别设定相应的连线属性。 图5.32 注意 例5.26中打印输出包含两条连线的图形 除非特别指定,否则每个SYMBOL语句设定的属性在一个 作图过程中只使用一次。 当同一图形中有多条连线时,为了区分连线的含义,需要使用选项 LEGEND添加图例。上述程序中,通过图形下方的图例,可以很清楚地 区分红线代表北美的销售额,黄线代表欧洲的销售额。 2.使用PLOT2语句叠加图形 使用PLOT2语句可以为其所指定的纵坐标变量在图的右侧设立一条 垂直坐标轴,这样不同的纵坐标变量就可以使用不同的纵轴尺度。 PLOT2语句的使用方法和PLOT语句一样。使用语法如下: PLOT2 纵坐标变量1*横坐标变量1 <纵坐标变量2*横坐标变量2 … > </ 选项>; 例5.27:使用PLOT2语句修改5.26的程序。 示例代码如下: axis1 order=(1990 to 2012 by 5) minor=(color=blue number=1); axis2 order=(13000 to 20000 by 1000) minor=(color=blue height=0.25 number=1); axis3 major=(number=8) minor=(number=1); symbol1 value=dot cv=red interpol=join ci=red; symbol2 value=diamond cv=green height=2 interpol=join ci=green line=10; proc gplot data=ex.sales_year; title 'Yearly Amount Series'; plot N_Amount*Year /legend haxis=axis1 vaxis=axis2; plot2 N_Transations*Year/legend vaxis=axis3; run; quit; goptions reset=all; 输出内容如图5.33所示。 图5.33 叠加图形 在上述程序中,需要注意以下几点: ·在GPLOT过程中使用PLOT2语句时,必须使用PLOT语句。 ·如果需要显示所有图形的图例,需要在PLOT语句和PLOT2语句中 都使用选项LEGEND,如图5.33所示。 ·使用PLOT2语句,可以使用选项VAXIS=专门设定右边坐标轴的属 性。 3.使用分组变量制作多条连线图 前面在介绍叠加图形时可看到,不同图形的纵坐标变量都存储在数 据集中的不同字段下。当纵坐标变量的数据都存储在数据集中的同一字 段下时,如何制作多条连线图?这时就需要指定第3个变量,根据第3个 变量的不同取值,在同一幅图中制作多条连线图,也称这第3个变量为 分组变量。其基本语法如下: PLOT纵坐标变量*横坐标变量=分组变量</选项>; 当使用分组变量时,系统默认提供图例。 例5.28:数据集ex.sales_year_by_area中是全球4个地区的历年销售 数量和销售金额的数据,包含4个变量:年份(Year)、销售数量 (Transactions)、销售金额(Amount)、地区(Area)。现在要在同 一幅图中制作多条连线图展现每个地区历年销售金额的情况。 示例代码如下: axis1 order=(1990 to 2012 by 5) minor=(color=blue number=1); axis2 minor=(color=blue height=0.25 number=1); symbol value=: height=2 interpol=join; proc gplot data=ex.sales_year_by_area; title 'Yearly Amount Series By Area'; plot Amount*Year=Area/haxis=axis1 vaxis=axis2 ; run; quit; goptions reset=all; 输出内容如图5.34所示。 图5.34 例5.28打印输出图形 在上述程序中,只使用了一个SYMBOL语句,定义了插值方法、散 点符号和大小,但是没有指定颜色。因此在图形中,每条连线都使用了 相同的散点符号,不一样的颜色。 注意 在SYMBOL语句中没有指定颜色时,该语句中定义的属性 将被重复使用,颜色按照选项COLORS=中指定的颜色或者系统颜色列 表中的颜色依次循环调用。 4.使用LEGEND语句设置图例属性 当一幅图形中有多条连线时,为了辨识每条连线的含义,可在 PLOT语句中使用选项LEGEND在图形中增加图例,此时选项LEGEND 使用了默认的方式显示图例。这里将介绍如何使用LEGEND语句对图例 的大小、布局、边框、位置及图例中文字的属性进行更加丰富的设置。 LEGEND语句的使用方法如下: LEGENDn 选项; 其中n=1~99,默认取值为1。LEGEND语句是全局语句,更新规则 和SYMBOL语句一样。 将某个LEGEND语句恢复为默认设置的方法如下: LEGENDn; 取消所有LEGEND语句的方法如下: GOPTIONS RESET=LEGEND; 如表5.8所示是一些常用选项,主要分为3大类别。 表5.8 LEGEND语句常用选项 在GPLOT过程中绘制多条连线图时,调用LEGEND语句设置的图 例属性的方法如下: LEGEND=LEGENDn; 例5.29:将例5.28中的图例放在连线图横坐标下面的中间部分,分 两排排列,并将图例区加上深蓝的边框和浅蓝的阴影。 示例代码如下: axis1 order=(1990 to 2012 by 5) minor=(color=blue number=1); axis2 minor=(color=blue height=0.25 number=1); legend1 cborder=blue cshadow=lightblue across=2 position=(bottom center) label= (color=lightpurple height=1.5 font='Courier New' 'Global'); symbol value=: height=2 interpol=join; proc gplot data=ex.sales_year_by_area; title 'Yearly Amount Series By Area'; plot Amount*Year=Area/haxis=axis1 vaxis=axis2 legend=legend1; run; quit; goptions reset=all; 输出内容如图5.35所示。 图5.35 例5.29打印输出图形 上述程序中使用LEGEND1语句设置了图例的属性,并在PLOT语句 中调用了LEGEND1。 5.3.4 制作气泡图 在GPLOT过程中可以使用BUBBLE语句制作气泡图。基本语法如 下: BUBBLE 纵坐标变量*横坐标变量=气泡变量 </ 选项> ; 气泡图有3个维度,包括:纵坐标变量、横坐标变量和气泡变量, 它们都是数据集中的变量,其中纵坐标变量和横坐标变量确定气泡的位 置,气泡变量确定气泡的大小,所以气泡变量必须是数值型变量。 例5.30:利用数据集ex.sales_year制作气泡图,反映欧洲地区历年的 销售情况。 示例代码如下: axis2 minor=(color=blue height=0.25 number=1); proc gplot data=ex.sales_year; title 'Yearly Sales Overview'; bubble E_Amount*Year=E_Transactions/vaxis=axis2 bcolor=red bsize=12; where year>=1999; run; quit; goptions reset=all; 输出内容如图5.36所示。 图5.36 例5.30打印输出气泡图 在上述代码中纵坐标为销售金额,横坐标为年份,销售数量决定了 气泡的大小,并且在BUBBLE语句使用了选项BCOLOR=和BSIZE=来设 定气泡的显示属性。在图5.36的方框中,两个气泡分别表示2010年和 2011年的销售情况,从销售金额来看,2011年比2010年有所上升(气泡 所处的位置升高),从销售数量来看,2011年比2010年下降了(气泡的 大小变化),由此可以推断,单位数量的销售金额从2010年到2011年提 高了。可见,使用气泡图能更加直观地反映多个维度的信息。 此外,BUBBLE语句中提供了更多的选项供读者自行定义气泡图的 显示属性,详见SAS帮助文档。 通过GCHART过程制作图形 5.4 本节介绍如何使用GCHART过程制作图形。GCHART过程展现的 是数据集的汇总信息,也就是统计量,这和前面介绍的TABULATE过 程相似。同样,TABULATE过程中关于分类变量和分析变量的用法在 这里也适用。 5.4.1 制作柱状图 使用GCHART过程制作柱状图的一般形式如下: PROC GCHART DATA=数据集; VBAR 变量1 <变量2 …> </选项>; VBAR3D 变量3 <变量4 …> </选项>; HBAR 变量5 <变量6 …> </选项>; HBAR3D 变量7 <变量8…> </选项>; RUN; QUIT; 其中: ·VBAR和VBAR3D是图形名,分别表示制作垂直柱状图和三维垂直 柱状图。 ·HBAR和HBAR3D也是图形名,分别表示制作水平柱状图和三维水 平柱状图,这里将VBAR、VBAR3D、HBAR、HBAR3D语句统称为作 图语句。 ·变量1、变量2、变量3等称为作图变量。根据它的不同取值,系统 将数据分为若干类别,分别用柱的长度或者高度展示分类数据的汇总结 果。默认情况下,柱的长度或者高度代表各类别的频数。作图变量可以 是数值型变量,也可以是字符型变量。 例5.31:利用数据集ex.sales_halfyear中的数据,分别制作简单的垂 直柱状图和水平柱状图展示每个州的交易频数。 示例代码如下: proc gchart data=ex.sales_halfyear; title 'Monthly Transaction Summary'; vbar state; vbar3d state; hbar state; hbar3d state; run; quit; 输出内容如图5.37和图5.38所示。 图5.37 例5.31输出垂直柱状图和三维垂直柱状图 图5.38 例5.31输出水平柱状图和三维水平柱状图 在以上代码中,用同一作图变量state制作了不同类型的汇总图形。 作图变量state是字符型变量,所以每一个州都会用一个柱来表示,柱的 高度和长度表示该州的交易频数。并且,我们也可以观察到,图形中柱 的排列次序是按照作图变量取值的升序排列的。在水平柱状图的右侧, 系统还默认输出了FREQ、CFREQ、PERCENT、CPERCENT等统计 量,分别表示频数、累计频数、频数百分比和累计频数百分比。 1.和作图变量相关的选项 从前面的例子中,我们已经知道当作图变量是字符型变量时,变量 的每个取值用一个柱来表示。当作图变量是数值型的,默认情况下,系 统将作图变量当成是连续性变化的,并按照变量的取值范围将其划分为 若干个等长的区间,每个区间用一个柱表示,每个柱的标签(在柱子的 底端)显示为区间的中点值(Midpoint)。 如表5.9所示是可以用来控制柱的排列次序、个数及显示内容的一 些选项。 表5.9 常见的用来控制柱的选项 例5.32:利用上述选项制作柱状图并分析选项的作用。 示例代码如下: proc gchart data=ex.sales_halfyear; title 'Monthly Transaction Summary'; vbar month/discrete ; vbar month/level=2 range; vbar month/midpoints=1 to 6 by 2; run; quit; goptions reset=all; 输出内容如图5.39所示。 由于month是数值型变量,取值为1~6,因此在第一个VBAR语句中 使用了选项DISCRETE,这使得在第一幅图中,每个月都由一个柱表 示。在第二个VBAR语句中,使用了选项LEVEL=2和RANGE,这让 month根据取值分为为2类,在显示的时候,在柱的底端显示的是month 的取值范围,而不是区间的中点值。 2.指定分析变量和统计量 和TABULATE过程一样,在GCHART过程中可以指定分析变量和 统计量,展示除频数以外的汇总数据。在作图语句中,可以使用选项 SUMVAR=和选项TYPE=来分别指定分析变量和统计量。 ·选项SUMVAR=的取值为数值型变量,当使用选项SUMVAR=而没 有使用选项TYPE=时,默认统计量为求和统计量。 ·选项TYPE=的取值可以为FREG、CFREQ、PERCENT、 CPERCENT、SUM或者MEAN中的一个。当选项TYPE=SUM或者 TYPE=MEAN时,必须使用选项SUMVAR=指定分析变量。 图5.39 例5.32打印输出图形 例5.33:制作柱状图显示各州销售金额汇总数据。 示例代码如下: proc gchart data=ex.sales_halfyear; title 'Sales Summary Across State'; hbar state/sumvar=amount type=sum descending; run; quit; goptions reset=all; 输出内容如图5.40所示。 在以上程序中,指定amount为分析变量,统计量为求和统计量,并 且使用选项DESCENDING让柱形按照销售金额从大到小排列。对于 (三维)水平柱状图,系统还在右边显示FREQ和SUM统计量的值。读 者也可以自行决定在(三维)水平柱状图的右边显示什么样的统计量, 可选统计量有FREG、CFREQ、PERCENT、CPERCENT、SUM和 MEAN,可以是其中的一个或者多个。如果不想显示统计量,可以使用 选项NOSTATS。 图5.40 例5.33打印输出图形 例5.34:制作柱状图显示各州销售金额汇总数据,并在右侧显示 SUM统计量和MEAN统计量的取值。 示例代码如下: proc gchart data=ex.sales_halfyear; title 'Sales Summary Across State'; hbar state/sumvar=amount type=sum descending sum mean; run; quit; goptions reset=all; 输出内容如图5.41所示。 图5.41 例5.34打印输出图形 在上述示例中,选项TYPE=SUM指定了柱状图中显示的统计量, 下一行的SUM MEAN指定了显示在图右边的统计量,在右边显示的统 计量可以和选项TYPE=指定的统计量不同。 对于(三维)水平柱状图,可以在右侧显示统计量的数值,对于 (三维)垂直柱状图,可以在柱的内部或外部显示统计量的数值,允许 的统计量也为选项TYPE=的6个统计量。 在柱外部显示统计量的数值时,使用方法如下: 统计量 或者 OUTSIDE=统计量 在柱内部显示统计量的数值时,使用方法如下: INSIDE=统计量 这里显示的统计量也可以和选项TYPE=中指定的统计量不同。 例5.35:将例5.34中的图形换成垂直柱状图,并在柱内部显示百分 比,在柱子外部显示销售金额。 代码如下: proc gchart data=ex.sales_halfyear; title 'Sales Summary Across State'; vbar state/sumvar=amount type=sum outside=sum inside=pct width=10; format amount dollar10.2; run; quit; goptions reset=all; 输出内容如图5.42所示。 图5.42 例5.35打印输出图形 这里分别通过选项OUTSIDE=和INSIDE=指定了显示在柱子外部和 内部的统计量数值,并且使用选项WIDTH=设定了柱子宽度。当系统默 认的柱子宽度不够显示统计量数值时,可以使用选项WIDTH=来设定柱 子宽度。 3.使用PATTERN语句设置柱状图的花纹和颜色 在上面的例子中,每个柱都是使用系统默认的颜色来填充的。实际 上,在使用GCHART过程制作柱状图时,可以对每个柱的颜色和花纹进 行设定,这是通过PATTERN语句实现的。PATTERN语句类似于 SYMBOL语句,也是全局语句。使用形式如下: PATTERNn 选项; 其中,n是不同PATTERN的序号,取值为1~255,缺省时默认取值 为1,如图5.43所示。PATTERN语句和前面介绍的全局语句一样,可以 出现在PROC步前面或PROC步的程序中间。 图5.43 柱子花纹图示 可以使用以下两种方法将所有PATTERN语句恢复为默认属性: GOPTIONS RESET=PATTERN; PATTERN1; 在PATTERN语句中用不同的选项来规定柱子不同的显示属性,常 用的选项如表5.10所示。 表5.10 PATTERN语句常用选项 在作图语句中可以使用选项PATTERNID=,设定根据变量的不同值 依次调用不同的PATTERN语句。 ·如果指定了颜色和花纹,则每个PATTERN语句设置的柱的显示属 性在一个GCHART过程中只使用一次。 ·如果只指定了颜色,则系统默认柱的花纹为实心填充。 ·如果只指定了花纹,没有颜色,则系统将依次使用选项COLORS= 中指定的颜色列表或者系统颜色列表中的颜色,而且会使用已经指定的 花纹。系统优先调用选项COLORS=中已经指定的颜色列表。 ·如果指定的PATTERN语句没有柱的数量多时,则系统将依次使用 选项COLORS=中指定的颜色列表或者系统颜色列表中的颜色。同样选 项COLORS=中指定的颜色列表优先。 例5.36:将例5.35中的每个柱子用不同的颜色表示。 示例代码如下: goptions colors=(verylightred verylightgreen verylightorange verylightpurple); pattern1 c=lightred; pattern2 c=lightgreen; pattern3 c=lightblue; pattern4 c=lightpurple; proc gchart data=ex.sales_halfyear; title 'Sales Summary Across State'; vbar state/sumvar=amount type=sum outside=sum inside=pct width=10 patternid=midpoint ; format amount dollar10.2; run; quit; goptions reset=all; 输出内容如图5.44所示。 图5.44 例5.36打印输出图形 在上述程序中,使用GOPTIONS语句和选项COLORS=指定了一个 颜色列表,并且定义了4个PATTERN语句。在GCHART过程中, PATTERNID=MIDPOINT表示根据作图变量的不同值来调用PATTERN 语句,可以观察到,第1个到第4个柱子依次使用PATTERN1语句到 PATTERN4语句中设置的属性。此外,由于柱的数量比已经定义的 PATTERN语句多,对于剩下的柱,系统优先依次使用选项COLORS=中 指定的颜色,所以第5~7个柱子为选项COLORS=中指定的第一个颜色 VERYLIGHTRED、第二个颜色VERYLIGHTGREEN、第三个颜色 VERYLIGHTORANGE。 5.4.2 制作分组柱状图 在上面的柱状图中,每个作图语句中都只使用了一个分类变量,即 作图变量。事实上,可以在作图语句中通过选项GROUP=和选项 SUBGROUP=指定其他的分类变量,从而制作分组柱状图。 1.使用选项GROUP=和SUBGROUP= 这两个选项的使用方法如下: GROUP=分组变量 SUBGROUP=子分组变量 使用选项GROUP=可以使系统让分组变量的每一个值都显示为一组 柱状图,使用选项SUBGROUP=使系统先按照作图变量作出一组柱状 图,再按照子分组变量的不同值将柱状图分为上下(或左右)相叠的部 分。例如: proc gchart data=ex.sales_halfyear; title 'Sales Summary Across State'; vbar state/sumvar=amount group=type; vbar state/sumvar=amount subgroup=type; run; quit; goptions reset=all; 输出内容如图5.45所示。 图5.45 GCHART过程中使用选项GROUP=和SUBGROUP= 在上述代码中,分别用两个VBAR语句制作了两幅图。第一幅图中 指定了GROUP=type,type在数据集中有两个取值,分别为Customerize 和Simple,系统根据type的每一个值分别制作了一组柱状图。第二幅图 中指定了SUBGROUP=type,每个州的柱子都分成Customerize和Simple 两个部分,并且这两部分相连(其中FL没有发生Simple产品的销售), 柱子的上面部分表示type=Simple,柱子的下面部分表示 type=Customerize。 需要注意的是,第一幅图中所有的柱子都显示为同一个颜色,不容 易辨认不同的组别。这时可以在作图语句中使用选项PATTERNID=,来 设定根据特定变量的不同值调用不同的PATTERN语句。除了上节介绍 的MIDPOINT,选项PATTERNID=还可以有多种不同的取值,如表5.11 所示。 表5.11 选项PATTERN=的不同取值与作用 在上述程序的第一个VBAR语句中添加了PATTERNID=GROUP, 可以使得柱状图按分组变量type的不同值显示不同的颜色。代码如下: vbar state/sumvar=amount group=type patternid=group; 2.使用AXIS语句设置坐标轴属性 前面已经介绍了如何使用AXIS语句设置坐标轴属性,在GCHART 过程中同样可以调用AXIS语句。使用方法如下: VBAR 作图变量/GROUP=分组变量 RAXIS=AXISm MAXIS=AXISn GAXIS=AXISk; 图5.46用图示的方法显示了选项RAXIS=、选项MAXIS=和选项 GAXIS=的作用区域。 例5.37:制作分组柱状图,按分组变量的不同值使用不同的颜色, 并设置坐标轴属性。 示例代码如下: axis1 order=(0 to 4000 by 1000) major=(height=2 ) minor=(color=blue number=2 height=0.5) label=(color=blue height=2 'Total Amount'); axis2 label=(color=verylightblue height=1.5 'State Across America'); axis3 label=(color=lightblue height=2 'Product Type'); proc gchart data=ex.sales_halfyear; title 'Sales Summary Across State'; vbar state/sumvar=amount group=type patternid=group raxis=axis1 maxis=axis2 gaxis=axis3; run; quit; goptions reset=all; 图5.46 不同选项的作用区域 图5.47 例5.37打印输出图形 输出内容如图5.47所示。 在上述代码中,首先使用AXIS语句分别设置了3个坐标轴属性,然 后在VBAR语句中,通过选项RAXIS=、MAXIS=和GAXIS=分别调用了 AXIS语句中设置的属性。 当使用选项SUBGROUP=时,同样可以使用前面5.3.3节介绍的 LEGEND语句和选项LEGEND=设置图例的属性,这里不赘述。 5.4.3 制作饼图 使用GCHART过程,除了可以制作(三维)柱状图,还可以制作饼 图。使用GCHART过程制作饼图的一般形式如下: PROC GCHART DATA=数据集; PIE 变量1 <变量2 …> </选项>; PIE3D 变量3 <变量4 …> </选项>; RUN; QUIT; 其中: ·PIE和PIE3D是图形名,分别表示制作饼图和三维饼图。 ·变量1、变量2、变量3等为作图变量。作图变量的取值代表着饼图 中的每一角,默认情况下,饼每一角的大小表示作图变量的取值频数。 和制作柱状图一样,作图变量同样可以是字符型变量和数值型变量。当 作图变量为数值型变量时,系统处理方法和制作柱状图时一样。 例5.38:利用数据集ex.sales_halfyear制作饼图,显示不同产品每个 销售人员的销售份额。 示例代码如下: proc gchart data=ex.sales_halfyear; title 'Sales Percentage'; pie emp_name/sumvar=amount type=pct group=type ; run; quit; goptions reset=all; 输出内容如图5.48所示。 图5.48 例5.38打印输出饼图 这里使用选项SUMVAR=指定了分析变量,选项TYPE=指定了统计 量为PERCENT。需要注意的是,和制作柱状图不一样,在制作饼图 时,选项TYPE=只有3个统计量可选,分别为FREQ(默认)、 PERCENT和SUM。 此外,还使用选项GROUP=指定了分组变量type,系统根据type变 量的两个取值分别制作了两个饼图。 在制作柱状图时所介绍的和作图相关的选项在这里同样适用。读者 如果有兴趣,可以参照SAS帮助文档深入学习。 在5.3节和5.4节中,介绍了作图时用到的众多选项和语句,这里稍 作总结。虽然GLOPT过程和GCHART过程所展现的数据不一样,一个 是通过散点图和连线图展现详细数据,一个是通过柱状图和饼图展现汇 总数据,但是图形的大部分元素是通用的,例如,标题、脚注、坐标 轴、图例等,因此对应的TITLE语句、FOOTNOTE语句、AXIS语句和 LEGEND语句在GPLOT过程和GCHART过程中都可以使用,如图5.49所 示。不同的是,在GPLOT过程中,使用SYMBOL语句来设置散点及连 线的属性;在GCHART过程中,使用PATTERN语句来设置柱或者饼的 属性。 读者如果想了解更多SAS/GRAPH的作图功能,可以参考SAS帮助 文档。参照图5.50可以方便地定位到相关的帮助文档。 图5.49 各种语句的作用区域 图5.50 SAS/GRAPH帮助文档位置 5.5 ODS输出传送系统 5.4节中介绍了可以通过更改系统中的设置来更改SAS报表和图形的 输出窗口,其实SAS提供了更加强大的Output Delivery System(输出传 送系统,ODS),使得用户可以更加方便、灵活地指定SAS系统中各个 PROC步和DATA步分析结果的目标输出窗口或文件地址。ODS可以应 用在SAS的所有软件中。 ODS输出对象包含2个部分,一部分是数据,另一部分是模板。 PROC步和DATA步提供的数据部分和系统内部自带的或用户自定义的 模板部分共同组成了ODS的输出对象。ODS包含两种输出对象:报表输 出对象和图形输出对象。这两种输出对象都可以被传送到以下4种ODS 目标中: ·PDF ·HTML ·RTF ·LISTING 在Windows环境下,从SAS 9.3开始,SAS默认的传送目标是 HTML,SAS的PROC步和SAS/GRAPH的结果都是通过“结果查看器”窗 口中的HTML文件来显示的。传送目标HTML一直处于打开状态,直到 用户自行提交代码关闭,或者通过系统设置关闭。图5.51显示了ODS的 处理流程,数据部分和模板部分组合形成了输出对象,ODS将输出对象 传送到了ODS目标,最后生成ODS输出文件。 图5.51 5.5.1 ODS输出传送系统处理流程 选择或剔除输出对象 先来运行一小段程序: proc contents data=sashelp.class; run; 默认情况下,当运行完PROC步后,所有的输出都被传送到HTML 中,并最终通过“结果查看器”窗口输出,如图5.52所示。 图5.52 CONTENTS过程输出内容 这时我们会发现,并不需要输出这么多表格,我们感兴趣的只是数 据集中变量的信息。怎么才能阻止系统输出其他信息呢? 1.使用输出对象跟踪功能 为了对输出对象进行选择或剔除,首先必须查询PROC步的输出中 都包含了哪些对象。要查询某个PROC步的输出对象,可以使用以下 ODS TRACE语句: ODS TRACE ON </选项>; ODS TRACE OFF; 其中: ·ODS TRACE语句是全局语句,ODS TRACE ON表示打开输出对象 跟踪功能,在跟踪功能打开后,运行的每个PROC步的输出对象都将被 写入日志中。 ·ODS TRACE OFF表示关闭输出对象跟踪功能。 ·ODS TRACE语句的选项包括LABEL和LISTING。选项LABEL表示 要求系统在日志中提供每个输出对象的路径标签。选项LISTING表示要 求系统将每个输出对象的跟踪结果显示在OUTPUT窗口的每个输出对象 前面。只有当传送目标LISTING打开时,选项LISTING才有效。 例5.39:打开跟踪功能重新运行CONTENTS过程。 示例代码如下: ods trace on/ label; proc contents data=sashelp.class; run; ods trace off; 日志如图5.53所示。 图5.53 CONTENTS过程运行日志 跟踪功能为每个输出对象提供的跟踪结果包括以下内容。 ·名称:表示输出对象的名称。 ·标签:表示输出对象的名称标签。 ·模板:ODS用于显示输出对象的模板信息。 ·路径:输出对象的路径。 ·标签路径:输出对象的路径标签。当使用选项LABEL时,在日志 中才会显示路径标签。 从结果窗口也可以看到PROC步对应的输出对象,如图5.54所示。 图5.54 CONTENTS过程“结果”窗口 2.选择和剔除输出对象 使用ODS SELECT语句和ODS EXCLUDE语句,可以针对各个ODS 目标更改选择列表或者剔除列表。使用语法如下: ODS ODS <ODS目标> <ODS目标> SELECT 输出对象1 <输出对象2 …> |ALL|NONE; EXCLUDE 输出对象1 <输出对象2 …>|ALL|NONE; 其中: ·ODS目标为指定具体的ODS目标,可以是HTML、LISTING、 OUTPUT、RTF、PDF或者PRINTER。只有当ODS目标处于打开状态 时,才可以指定具体的ODS目标。默认情况下,由系统修改OVERALL 的选择列表或者剔除列表。 ·ALL表示选择或剔除所有输出对象。 ·NONE表示不选择或剔除任何输出对象。 输出对象可以是路径或者路径标签,也可以是部分路径或者部分路 径标签,如图5.55所示。 图5.55 输出对象 主要有以下几种。 ·路径:如Contents.Datasets.Variables。 ·部分路径:指的是路径中从某一个(.)号开始到路径结束的部 分,如Datasets.Variables或Variables。 ·标签:标签必须用引号括起来,如‘变量’。 ·标签路径:格式为'过程步名称'.'逻辑库.数据集名称'.'变量',例 如,'Contents PROCEDURE'.'SASHELP.CLASS'.'变量'。 ·部分标签路径:指的是标签路径中从某一(.)号开始到标签路径 结束的部分,如'SASHELP.CLASS'.'变量'。 ·路径和标签的混合形式:将路径和标签混合起来使用,例如, Contents.Datasets.'变量'。 默认情况下,在每一个DATA步或者PROC步结束时,ODS都会清 空输出对象选择列表和剔除列表。 注意 在使用ODS时,强烈建议在每一个PROC步和DATA步结 束时都使用RUN语句,在SQL过程、DATASETS过程和SAS/GRAPH中 的PROC步结束时都使用QUIT语句,以避免因界限不明发生错误。 如果要使得某个输出对象在DATA步或PROC步结束时,继续留在 选择列表或者剔除列表中,可以在这个输出对象的后面紧跟着使用选项 PERSIST,选项PERSIST必须用括号括起来。使用方法如下: 输出对象(PERSIST) 需要注意的是,选项PERSIST可以使得紧贴其前面的输出对象一直 保留在选择列表或者剔除列表中,直到使用ODS SELECT语句指定新的 列表为止。 例5.40:针对不同的ODS目标选择不同的输出对象列表。 示例代码如下: ods listing; /*modify OVERALL selection list*/ ods select moments; /*modify HTML selection list*/ ods html select Quantiles; proc univariate data=sashelp.prdsale; var actual; run; ods listing close; 在上述程序中,使用了UNIVARIATE过程,用来计算和输出数据描 述性统计量的值(第9章会详细介绍该PROC步)。由于默认情况下,系 统仅将PROC步的运行结果传送到HTML中,因此这里先使用ODS LISTING语句把传送目标LISTING打开,随后修改了OVERALL的选择 列表和HTML的选择列表,最后使用ODS LISTING CLOSE语句关闭了 传送目标LISTING。图5.56和图5.57分别是“结果查看器”窗口和 OUTPUT窗口的输出结果。 图5.56 结果查看器窗口 图5.57 OUTPUT窗口输出内容 当PROC步运行产生一个输出对象时,ODS会根据各个ODS目标的 输出对象列表和OVERALL的输出对象列表确定是否将这个输出对象传 送到具体的ODS目标中。处理逻辑如下: ·对于某个ODS目标,ODS首先检查这个ODS目标是否存在输出对 象列表,如果有,则根据列表确定是否将该输出对象传送到这个ODS目 标中。 ·对于某个ODS目标,如果不存在输出列表,则根据OVERALL的输 出列表确定是否将该输出对象传送到这个ODS目标中。 根据这一原则,上述程序的处理步骤如下: 1)HTML的输出对象列表中仅有Quantiles一个输出对象, OVERALL的输出对象列表中仅有Moments。 2)在UNVARIATE过程运行中产生输出对象Moments时,对于 HTML,ODS检查HTML的输出对象列表,由于这里HTML的输出列表 中仅包含Quantiles,因此输出对象Moments不传送到HTML中;对于 LISTING,ODS先检查LISTING的输出列表,由于LISTING的输出列表 不存在,ODS会继续检查OVERALL的输出列表,并且OVERALL的输 出列表中包含了Moments,确定传送到LISTING中,并最终通过 OUTPUT窗口输出。 3)在产生输出对象Quantiles时,对于HTML,ODS检查HTML的输 出对象列表,由于这里HTML的输出列表中包含Quantiles,因此输出对 象Quantiles传送到HTML中,并最终通过HTML窗口输出;对于 LISTING,由于LISTING的输出列表不存在,ODS继续检查OVERALL 的输出列表,由于OVERALL的输出列表中仅包含Moments,因此输出 对象Quantiles不传送到LISTING中。处理完毕。 所以,在上面的例子中,我们最终看到HTML窗口中输出了 Quantiles,OUTPUT窗口中输出了Moments。 表5.12列出了默认情况下各个ODS目标的输出对象列表。 表5.12 默认情况下各个ODS目标的输出对象列表 3.在日志中中显示选择或者剔除对象列表 想要查看已经设定的关于输出对象的选择,可以使用以下语句: ODS <ODS目标> SHOW; ODS目标可以是HTML、LISTING、OUTPUT、RTF、PDF或者 PRINTER。当没有指明ODS目标时,日志中显示OVERALL的选择或者 剔除目标列表。 5.5.2 创建多种格式输出文件 在通过ODS SELECT语句或者ODS EXCLUDE语句选择了输出对象 后,可以把这些输出对象传送到ODS目标,并且输出到多种格式的输出 文件中,包括RTF文件、PDF文件、HTML文件、EXCEL文件和SAS数 据集。 1.创建RTF文件 RTF(Rich Text Format)文件包含了格式属性和字符属性的信息, 可以通过很多文字处理软件进行读写,这也是一种广泛使用的文件格 式,以.rtf结尾。在SAS中,可以使用ODS RTF语句来创建RTF文件,使 用方法如下: ODS RTF FILE='文件路径/文件名.RTF' <选项> ; SAS代码; ODS RTF CLOSE; 说明: ·第一行ODS RTF语句将传送目标RTF打开,并在FILE=后面指定输 出文件存储路径和文件名称。 ·最后一行ODS RTF语句关闭传送目标RTF。 例5.41:将下列PROC步生成的报表存储到.RTF文件中。 示例代码如下: ods html close; ods rtf file="C:\Users\vdmrace\Desktop\data\prdsale.rtf" style=science; ods rtf select moments quantiles; proc univariate data=sashelp.prdsale; var actual; run; proc gchart data=sashelp.prdsale; hbar country /group=prodtype sumvar=actual patternid=group; run; quit; ods rtf close; ods html; 在ODS RFT语句中使用了选项STYLE=指定该文件存储成SCIENCE 风格。PROC步后面的ODS RFT CLOSE语句将传送目标RTF关闭。RTF 文件内容如图5.58所示。 图5.58 注意 例5.40生成的RTF文件 由于系统默认传送目标HTML一直处于打开状态,因此在 不需要使用HTML输出时,为了节省资源,可以将HTML关闭。 2.创建PDF文件 PDF(Portable Document Format)是由Adobe Acrobat公司独立开发 的一种文件格式,使用广泛,可包含书签和链接,可以通过Acrobat阅 读器浏览。在SAS中,可以使用ODS PDF语句来创建PDF文件,使用方 法如下: ODS PDF FILE='文件路径/文件名.PDF' <选项> ; SAS代码; ODS PDF CLOSE; 其中: ·ODS PDF语句的选项有NOTOC、STARTPAGE=、UNIFORM。 ·用户可以使用系统选项BOTTOMMARGIN=、LEFTMARGIN=、 ORTIENTATION=、RIGHTMARGIN=、TOPMARGIN=来进行页面设 置。读者若有兴趣可以阅读SAS帮助文档。 例5.42:将下列PROC步生成的报表存储到.PDF文件中。 示例代码如下: proc sort data=sashelp.prdsale out=prdsale; by country; run; ods html close; ods pdf file="C:\Users\vdmrace\Desktop\data\prdsale.pdf"; proc univariate data=prdsale; by country; var actual; run; ods pdf close; ods html; 在UNIVARIATE过程中使用了BY语句,所以在UNVARIATE过程 之前需要将数据集按照BY变量进行排序。生成的PDF文件如图5.59所 示。 3.创建增强型HTML文件 通常情况下,如果用户没有主动通过代码或者系统设置关闭传送目 标HTML,HTML会一直处于打开状态。使用HTML语句可以自己定义 HTML输出文件的框架、内容、主体,其使用语法如下: ODS HTML <PATH='文件路径'(URL=NONE)> BODY='BODY文件名.HTML' <FRAME='FRAME文件名.HTML'> <CONTENTS='CONTENTS文件名.HTML'>; SAS 代码; ODS HTML CLOSE; 其中: ·选项PATH=指明存放文件的目录,其后的子选项(URL=NONE) 表示当生成的HTML文件中包含链接时,使用相对路径。当需要在其他 服务器上使用在某台服务器或操作环境中生成的HTML文件时,必须使 用这一选项。 ·选项BODY=指明HTML输出中包含主体的文件名称。这里也可以 使用选项FILE=来代替BODY=。如果没有使用选项PATH=,在选项 FILE=或者BODY=指定的文件名中也可以加入路径信息。 ·选项FRAME=指定HTML输出中的包含框架的文件名称。 ·选项CONTENTS=指定HTML输出中的包含目录的文件名称。 图5.59 例5.41生成的PDF文件 例5.43:将下列PROC步生成的报表存储到.HTML文件中,同时保 存文件的框架和目录信息。 示例代码如下: proc sort data=sashelp.prdsale out=work.prdsale; by country; run; ods html path='C:\Users\vdmrace\Desktop\data' body='prdsalebody.html' frame='prdsaleframe.html' contents='prdsalecontents.html' ; proc tabulate data=work.prdsale; class region division prodtype; var actual; keyword all sum; keylabel all='Total'; table (region all)*(division all), (prodtype all)*(actual*f=dollar10.) / misstext=[label='Missing'] box= [label='Region by Division and Type']; run; ods select ExtremeObs Quantiles Moments; proc univariate data=work.prdsale; by Country; var actual; run; ods html close; prdsaleframe.html文件如图5.60所示。左边是HTML目录,右边是 HTML主体文件,HTML框架将目录和主体文件结合起来显示。 图5.60 例5.42生成的HTML文件 需要注意的是,当传送目标HTML打开后,如果使用选项BODY= 或者FILE=指定了文件主体,其后所有的输出报表和图形都将被写入这 个文件主体中,直到遇到以下3种情况中的一种为止。 ·ODS HTML CLOSE语句,HTML目标被关闭。 ·新的ODS HTML语句,并且包含选项BODY=或者FILE=。 ·选项NEWFILE=将输出指定到其他文件。 选项NEWFILE=的取值可以为NONE(默认设置,仅生成一个文 件)、PROC(表示每个PROC步生成一个文件)、OUTPUT(表示每个 输出对象生成一个文件)、BYGROUP(每个BY组合生成一个文件) 等。当使用选项NEWFILE=时,如果生成新的文件,新文件的名字后面 会从1开始依次加上序号(第一个文件不加序号)。选项NEWFILE=在 RTF目标中同样可以使用。 例5.44:将例5.42中不同PROC步生成的报表存储到不同的HTML文 件中。 分析:只需在ODS HTML语句中加入选项NEWFILE=PROC即可。 代码如下: ods html path='C:\Users\vdmrace\Desktop\data' body='prdsalebody.html' frame='prdsaleframe.html' contents='prdsalecontents.html' newfile=proc; 这里生成了两个HTML主体文件,一个是prdsalebody.html,包含了 TABULATE过程的输出对象,另一个是prdsalebody1.html,包含了 UNIVARIATE过程的所有输出对象。如图5.61所示。 图5.61 例5.43生成的多个HTML文件 4.创建SAS数据集 除了可以生成第三方格式的文件以外,用ODS OUTPUT语句还可以 为许多PROC步的输出对象建立SAS数据集,它可以包含输出中的每个 统计量,其基本形式如下: ODS OUTPUT 输出对象1=数据集1 <输出对象2=数据集2 …>; 其中: ·输出对象可以是路径或者路径标签,也可以是部分路径或者部分 路径标签,具体规则和ODS SELECT语句中一样。 ·ODS OUTPUT语句中可以设置输出多个数据集。 关闭传送目标OUTPUT的语法如下: 例5.45:将下列PROC步生成的报表存储在SAS数据集中。 示例代码如下: ods output ExtremeObs=work.ExtremeObs Moments=work.Moments ; proc univariate data=sashelp.prdsale; var actual predict; run; ods output close; 在上面的代码中,只指定了部分路径,如果打开输出对象查询跟踪 功能,我们会发现部分路径为ExtremeObs的输出对象有两个,分别为 Univariate.ACTUAL.ExtremeObs和Univariate.PREDICT.ExtremeObs,但 是最后只生成了一个数据集work.ExtremeObs,系统会将具有相同部分 路径的不同输出对象迭加起来,输送到同一个数据集中。数据集 work.Monments也是同样的。数据集内容如图5.62所示。 图5.62 例5.44生成的数据集的内容 这是由于在ODS OUTPUT语句中指定输出对象时只使用了部分路 径。然而有时将不同的输出对象输出到同一数据集中会带来不必要的麻 烦,这时可以在输出对象后面使用选项MATCH_ALL,阻止将不同输出 对象输出到同一数据集中。使用这一选项,可以使具有相同部分路径名 的数据集分别输出到不同的数据集中,数据集的名字后面会从1开始依 次加上序号(第一个数据集不加序号)。如果将选项MATCH_ALL更改 成MATCH_ALL=宏变量名,则各个数据集的名字都将被存储到指定的 宏变量中(第7章中会详细介绍SAS宏变量的使用)。 例5.46:在例5.44的基础上使用选项MATCH_ALL,使得不同输出 对象输出到不同数据集中。 示例代码如下: ods output ExtremeObs(match_all=esets) =work.ExtremeObs Moments(match_all=msets) = work.Moments; proc univariate data=sashelp.prdsale; var actual predict; run; ods output close; %put esets=&esets. Msets=&msets.; 这里生成了4个数据集,如图5.63所示。 图5.63 例5.45生成的数据集 在日志中输出了两个宏变量esets和msets的取值,如图5.64所示。 图5.64 例5.45运行产生的部分日志 注意 PRINT过程和REPORT过程不支持ODS OUTPUT语句。 上面介绍了ODS的一些最基本的功能。此外,ODS还提供了很多其 他功能,如用户可以修改系统提供的模板,甚至可以自己定制模板。有 兴趣的读者可以阅读SAS帮助文档,了解更多关于SAS ODS的内容。 5.6 本章小结 在SAS中对数据进行汇总与展现主要有两种方式:一种是列表,一 种是图形。本章首先介绍了如何运用PRINT过程和TABULATE过程制作 各种类型的报表和汇总报表;然后介绍了如何运用GPLOT过程和 GCHART过程制作散点图、连线图、气泡图、柱状图等多种图形;最后 简要介绍了ODS输出传送系统,包括如何选择或剔除输出对象,创建多 种格式的输出文件等。 第6章 SAS SQL语言 结构化查询语言SQL(Structured Query Language)是关系型数据库 管理系统的标准语言。绝大多数主流的关系型数据库管理系统,如 Oracle、Microsoft SQL Server和DB2等都使用了SQL语言,并在此基础 上各自对标准的SQL进行了扩展。SAS系统也支持SQL语言。本章将介 绍如何使用SAS SQL对数据集进行检索、加工以及管理。要说明的是, 这里所提到的SQL语言均指SAS SQL语言。 6.1 SQL语言概述 在SAS中使用SQL时,在表达同一个意思或表述同一种形式时,其 中某些术语与SAS术语略有不同,如表6.1所示。 表6.1 SAS术语与SQL术语的区别 SQL语言在SAS中是通过PROC SQL来实现的。使用PROC SQL, 用户可以实现以下功能: ·制作报表与表。 ·生成一些统计性数据,如均值、求和等。 ·合并表。 ·从其他表中抽取部分数据,如部分行和列。 ·更新表的行。 ·更新表的列,如新增或者删掉某个列等。 ·从其他的数据管理系统(DBMS)中更新或者抽取数据。 总之,SQL语言是一个强大的数据处理工具。需要指出的是,SQL 语言不是DATA步的替代,而是DATA步的一种补充。从理论上说,任 何用SQL实现的任务都可以用DATA步来实现,但是,SQL语言更加直 观、简洁,甚至在某些情形下,SQL可以实现需要多个DATA步才能实 现的任务。下面列举几个使用SQL比较方便的情形: ·合并表时不需要事先对表进行排序。 ·生成宏变量,尤其是生成多个宏变量(有关宏变量的内容会在第7 章讲述)。 ·对多个表进行匹配。 ·生成一些统计量,如平均数、求和。 ·生成计数,如计算表中有多少列的值非空。 使用SQL检索数据 6.2 使用SQL可以很方便地从表中检索数据,如选择符合条件的行与 列,根据已有的列生成新的列,对数据分组统计信息、排序等。用户提 交一段用SQL编写的检索程序,经SAS执行后,PROC SQL自动以报表 的形式在OUTPUT中显示结果。需要指出的是,除了本章后面提到的使 用SQL对表进行管理的操作外,其他使用SQL对表进行的操作不会影响 原表。因此,使用SQL既可以查看操作的结果又可以避免生成中间数据 集。当然,SQL也允许用户将当前OUTPUT窗口中的报表创建成一个 SAS数据集。 6.2.1 SQL的基本结构 在SAS编程语法中,一个语句是一段触发SAS系统执行某些操作或 者将某种信息传递给系统,并以分号结束的代码。在SQL中,一个语句 可能包含若干个从句,从句间以空格隔开。例如,在SQL中,SELECT 表示从指定的表中选出某些列,FROM指定了待操作的表格,二者为相 互独立的从句(SAS不单独执行它们),两者结合在一起则构成了一个 可执行的语句。 SQL的基本结构如下: PROC SQL; SELECT表1.列1,表1.列2, … FROM 表1 <WHERE> <GROUP BY> <ORDER BY>; QUIT; 其中: ·SELECT从句选择要查看的列,不同列之间用逗号隔开。 ·FROM从句指定了操作的表。 ·WHERE从句选择满足条件的行。 ·WHERE、GROUP BY和ORDER BY从句若不需要可以不出现。 其实,在SQL的基本结构中,QUIT语句并不是必须的,但是建议 在完成任务后以QUIT语句结束当前的PROC SQL。SAS执行完PROC SQL任务,如果后面没有其他DATA步或PROC步,PROC SQL就不会退 出。此时,SAS状态栏会一直显示running,如图6.1所示。 图6.1 SAS状态栏一直显示running 6.2.2 使用SQL对列进行操作 上面介绍了PROC SQL的基本结构。从本节开始,会具体介绍 PROC SQL的基本知识。下面先介绍PROC SQL对列的操作,即SELECT 从句。 从句SELECT选择所需要的列,其使用语法如下: SELECT 表名称.列1, 表名称.列2,... 在不混淆的情况下,表名称可以省略。这里混淆指的是,选择的列 出现在多个表中,此时,仅依据列名称无法判断具体指的是哪个表的 列。对单表的操作则不涉及该情况,表名可以省略。此外,SELECT从 句中,表名称前不加库名,库名在后续的FROM从句中指定。 如果用户要选择表中的所有列,可以一一列出所有列的名称,也可 以用以下语法: SELECT表名称.* 用户可以使用关键字AS对列重命名,也可以重新定义属性,所有 的属性可以加在列名称后,用空格隔开。以下命令会将列1的名称修改 为“新名称”,FORMAT和LABEL选项分别修改列的格式和标签。 SELECT表名称.列1 AS 新名称 FORMAT = LABEL= ,表名称.列2, ... 除了对表格重新命名外,用户还可以根据原表中的列生成新的列, 具体见例6.1。 例6.1:表sashelp.cars包含了汽车生产商制造的各种型号汽车的数 据。下面使用SQL语句输出以下3列:Make(生产商)、Model(型 号)以及MSRP(建议零售价)。同时生成一个新列Tax(税金),其 值为MSRP*0.06。 proc sql; title "Generating A New Column"; select cars.Make, cars.Model, cars.MSRP, cars.msrp*0.06 as tax from sashelp.cars; quit; 代码中使用了TITLE语句为输出的报表生成标题。一般来说, TITLE语句的位置可以在PROC SQL之前,也可以位于SELECT从句之 前。同样地,用户可以使用FOOTNOTE在报表中添加脚注。上述代码 的部分输出结果如图6.2所示。 图6.2 注意 代码的部分输出结果 SELECT从句中列的先后顺序决定了在输出报表中它们的 输出顺序。 6.2.3 使用SQL对行进行操作 1.DISTINCT关键字 如果PROC SQL语句中没有WHERE从句,那么SAS输出全部行(包 括原表中重复的行)。若用户不希望输出重复的行,可以在SELECT语 句中使用DISTINCT关键字。由于DISTINCT的作用是针对SELECT从句 中的所有列而言的,因此一个SELECT从句最多只能包含一个 DISTINCT,其位置紧随SELECT之后。关键字DISTINCT的使用格式如 下: SELECT DISTINCT表名称.列1, 表名称.列2,... 例6.2:根据以下代码,比较使用与不使用关键字DISTINCT输出结 果的区别。 proc sql; title "不使用distinct关键字"; select make from sashelp.cars; title "使用distinct关键字"; select distinct make from sashelp.cars; quit; 部分输出结果如图6.3所示。 图6.3 两种输出结果 2.WHERE从句 与DATA步类似,SQL可以实现逻辑比较符号、逻辑关系符号以及 逻辑运算符号与WHERE从句一起使用,来选择符合条件的行。下面是 WHERE从句的使用示例。 SQL可以通过WHERE从句,并结合适当的逻辑比较符号、逻辑关 系符号和逻辑运算符号来一起使用,从而选择符合条件的行。下面是 WHERE从句的使用示例。 例6.3:修改例6.1中的代码,使其输出MRSP不大于40000的行。 示例代码如下: proc sql; select cars.make, cars.model, cars.msrp, cars.msrp*0.06 as tax from sashelp.cars where msrp<=40000; quit; 其输出结果的前5行如图6.4所示。 图6.4 例6.3输出结果的前5行 在例6.3中,选择了MRSP不大于40000的行。根据列tax的定义,这 些行也可以通过条件“tax<=2400”来得到。但是如果用户在WHERE从句 中直接使用该条件,SAS会提示错误。比如,用户试提交下述代码: proc sql; select cars.make, cars.model, cars.msrp, cars.msrp*0.06 as tax from sashelp.cars where tax <= 2400; quit; 将会在日志中显示如图6.5所示的错误信息。 图6.5 错误信息 错误的原因是SQL试图在原表sashelp.cars中搜寻列tax,但列tax在原 表中并不存在,它是用户新生成的列。对于新生成列,用户都必须在前 面加上关键字CALCULATED来表明该列是新生成的。来看以下示例。 例6.4:下面的代码中使用了关键字CALCULATED,其输出结果和 例6.3一样。 示例代码如下: proc sql; select cars.make, cars.model, cars.msrp, cars.msrp*0.06 as tax from sashelp.cars where calculated tax <= 2400; quit; 3.ORDER从句 在SQL中可以使用ORDER从句使输出的报表按照某些列来进行排 序。默认情况下,PROC SQL按照指定列的升序排列,若用户希望按降 序排序,可在该列名后加上关键字DESC。ORDER从句允许用户按照多 个列的升(降)序排列,多个列之间多逗号隔开。ORDER从句的使用 语法如下: ORDER BY 列1<DESC>,列2<DESC>,...; 例6.5:修改例6.4中的代码,使其依次按照MSRP的升序、Make的 字母降序和Model的字母升序排列。 示例代码如下: proc sql; select cars.make, cars.model, cars.msrp, cars.msrp*0.06 as tax from sashelp.cars where calculated tax<=2400 order by msrp, make desc, model; quit; 其部分输出结果如图6.6所示。 图6.6 4.GROUP BY从句 例6.5的部分输出结果 在SQL语言中,用户可以通过GROUP BY从句来查看分组信息。需 要注意的是,GROUP BY语句一般和汇总函数(summary function)配 合使用,若用户在SELECT从句中不添加任何汇总函数,那么GROUP BY从句会被当成ORDER BY从句来使用。表6.2列举了常见的汇总函数 及其含义。 表6.2 常见的汇总函数及其含义 在表6.2中,COUNT函数的使用相对灵活。接下来,将重点讲述 COUNT函数的使用。在SQL中,使用COUNT函数可以很方便地进行计 数,其使用命令如下: COUNT(列名) 其中: ·列名是“*”的时候,COUNT函数返回的是表中的行数。 ·列名前面可以加上DISTINCT,这样重复的行只会被计算一次。 例6.6:使用SQL计算表sashelp.cars中不同厂商的个数。 下述代码使用count(distinct make)来计算不同厂商的个数。由于 使用了关键字DISTINCT,因此重复的行只被计算一次。 示例代码如下: proc sql; select count(distinct make) as number_of_maker from sashelp.cars; quit; 输出结果如图6.7所示。 图6.7 输出结果 若去掉DISTINCT,输出的结果将会是428而不是38。此外,除了使 用“*”以外,COUNT函数中的列只能有一个(这点不同于SELECT从 句,它可以选择多个列)。例如,在例6.6中,如果用户希望计算表 sashelp.cars中所有不同厂商生产的汽车型号的数目,直接使用COUNT 函数会出现语法错误,如图6.8所示。 图6.8 直接使用COUNT函数出现语法错误 解决办法之一是使用CATS函数,用它连接列Make和Model,再进 行计数。函数CATS的使用语法如下: CATS(列1, 列2, …); 其作用是连接括号里面的列,同时删去头尾空格。CATS函数中的 列可以是变量、字符或者数值常量、表达式。有关CATS函数的详细介 绍参见SAS帮助文档。 例6.7:使用SQL计算表sashelp.cars中不同厂商和它们生产车型的组 合总数。 这里将采用COUNT函数并结合CATS函数来计算多列的组合。示例 代码如下: proc sql; select count(distinct cats(make, model) ) as N_combination from sashelp.cars; quit; 输出结果如图6.9所示。 图6.9 不同厂商和车型组合数 例6.8:计算表sashelp.cars中每个汽车生产厂商销售的所有型号汽车 的建议零售价(MSRP)的平均值,并把它们按照升序排列。 这里使用GROUP BY语句对表中的行进行分组操作。示例代码如 下: proc sql; select make , avg(msrp) as average_price from sashelp.cars group by make order by calculated average_price; quit; 上述代码的部分输出结果如图6.10所示。 图6.10 例6.8的部分输出结果 5.HAVING从句 类似于WHERE语句,HAVING语句也是用来选择满足特定条件的 行的,二者不同之处在于:WHERE从句的操作在SELECT从句前,而 HAVING从句的执行在SELECT与GROUP BY之后。因此,涉及GROUP BY时,只能使用HAVING从句。此外,在没有GROUP BY从句的情况 下,HAVING从句可以代替SELECT从句。由于HAVING从句在 SELECT之后执行,因此对于SELECT从句中新生成的列,无需加关键 字CALCULATED。 例6.9:在表sashelp.cars中,计算每个汽车厂商销售所有型号汽车的 建议零售价(MSRP)的平均值,输出平均值小于20000的行,输出结果 按照厂商的字母排序。 此处使用HAVING语句。示例代码如下: proc sql; select make , avg(msrp) as average_price from sashelp.cars group by make having average_price <= 20000 order by make; quit; 部分输出结果如图6.11所示。 图6.11 注意 例6.9的部分输出结果 在例6.9的第二个语句中,各个从句的位置是固定的,不 能对调,如HAVING从句和ORDER BY从句的位置不能对调。 6.2.4 使用SQL对报表加工与生成数据集 SQL提供了一些系统选项来对输出的报表进行加工,此外,SQL语 言还允许用户将报表存储成SAS数据集。下面先来介绍一下SQL中制作 报表的一些选项。 1.NUMBER|NONUMBER选项 默认情况下,SQL在输出报表的时候不输出行数(比较:proc print 默认输出行数)。用户如果希望看到行数,可以在PROC SQL语句中加 入NUMBER选项。其语法格式如下: PROC SQL NUMBER; 2.OUTOBS和INOBS选项 SQL通过OUTOBS=选项,允许用户指定输出表中的前若干行。其 语法格式如下: OUTOBS = N 其中,N为指定输出行数。 选项INOBS用来控制读入表的行的数目。比如,以下选项读入表的 前N行: INOBS = N 例6.10:下述代码读入表sashelp.cars,仅输出不同厂商和汽车型号 组合的前10个,并加上行数与相应的标题和脚注。 示例代码如下: proc sql outobs = 10 number; title "The First Ten Car Models In The List"; select distinct cars.make, cars.model from sashelp.cars; quit; 其输出结果如图6.12所示。 图6.12 例6.10的部分输出结果 如果用户希望将查询到的数据存储成SAS数据集,可以在PROC SQL中加入CREATE从句。其格式如下: CREATE TABLE 库名.表名AS CREATE TABLE库名.表名AS 例6.11:在SQL中创建表,将例6.10中输出的数据保存成SAS数据 集。 示例代码如下: proc sql outobs = 10 number; title "The First Ten Car Models In The List"; create table work.fist_ten_models as select distinct cars.make, cars.model from sashelp.cars; quit; 执行上述代码,在WORK逻辑库内可以看到新建的数据集 Fist_ten_models,如图6.13所示。 图6.13 6.2.5 数据集Fist_ten_models 子查询 所谓的子查询是指在查询语句内部嵌套一个查询语句。按照嵌套的 查询语句与原语句关联与否,子查询可分为以下两种。 ·不相关子查询:子查询与原查询无关。 ·相关子查询:子查询与原查询相关。 例6.12:表spending中的列max_spending包含了被调查人用于买车的 最大支出。表sashelp.cars中包含了车商生产的各种型号的车,以及建议 零售价。试用PROC SQL生成一个报表,要求该报表仅包含平均建议零 售价不超过max_spending均值的汽车制造商。 这里采用不相关子查询,示例代码如下: data work.spending; input ID max_spending; datalines; 1 16000 2 20000 3 24000 4 18000 5 23000 ; run; proc sql; title " Recommended Brands"; select cars.make, avg(cars.msrp) as avg_msrp from sashelp.cars group by cars.make having avg(cars.msrp) <= (select avg(max_spending) from work.spending); quit; 上述代码中,在HAVING从句的逻辑表达式中用到了一个子查询: select avg(max_spending)from work.spending。PROC SQL优先处理子 查询,即SQL首先从表spending中计算出avg(max_spending),然后把 该值返回原查询语句中进行处理。该子查询的处理仅依赖表spending, 与原表sashelp.cars无关,这是一个不相关子查询。PROC SQL输出结果 如图6.14所示。 实际中,更经常遇到的情况是子查询的处理涉及原查询。例如,表 Donors包含了所有捐赠人的姓名和捐赠额;表Donors_Current仅包含今 年捐赠人的姓名,具体如图6.15所示。 图6.14 图6.15 PROC SQL的输出结果 表Donors和表Donors_Current 二者的交集为今年捐赠人的姓名和金额。若把二者的交集作为一个 子查询,那么这个查询就是相关子查询。 例6.13:利用PROC SQL输出表Donors中不是今年捐赠人的姓名以 及捐赠额。 示例代码如下: proc sql; title "Donors From Other Years"; select * from donors where not exists( select name from donor_current where donor_current.name = donors.name); quit; 在上述代码中,括号内为二者的交集,是一个相关子查询。该子查 询的结果是表donors中属于donor_current的所有名字,即表donors中今年 捐赠人的名字。子查询的结果将返回到原查询中。关键词NOT EXISTS 仅从表donors中查询出不在子查询结果中的行,即表donors中不是今年 捐赠人的名字与金额。输出结果如图6.16所示。 图6.16 输出结果 使用SQL对表进行横向合并 6.3 对多个表格进行合并(包括横向合并与纵向合并)的实质是对两个 表格进行合并,因此在本节中,仅考虑两个表格的情况。此外,使用 SQL对表进行横向合并可分为内连接(INNER JOINS)与外连接 (OUTER JOINS)。 6.3.1 使用SQL对表进行内连接 所谓的内连接指的是,在对表进行横向连接的时候,根据连接的条 件,仅返回两个表中所有匹配的数据。 对表A和表B最简单、最基础的横向连接是生成两表的卡氏积。卡 氏积可以看作是内连接的一种特殊情况。表A和表B的卡氏积表是指: 表A中的行和表B中的行所有可能的组合。因此,假设A和B的行数分别 为M和N,那么生成的卡氏积表的行数为M×N。表A、表B及它们生成的 卡氏积表如图6.17所示。 图6.17 注意 表A、表B及它们生成的卡氏积表 生成卡氏积的表会随着原来表行数的增加而急剧增加。例 如,两个10行的表,生成的卡氏积表为100;但是,两个100行的表所生 成的卡氏积表将会为10000行。 两表的卡氏积表输出的是两表的所有可能组合。在实际应用中,这 种没有选择地输出所有结果的意义不大。更多的情况是,需要有选择地 输出卡氏积表的一些观测。内连接是常见的、用于输出满足条件的部分 卡氏积表观测的一种方法。其使用语法如下: PROC SQL; SELECT 表1.列1,表1.列2 ,…,表2.列1,表2.列2,… FROM 表1,表2 WHERE 从句 <其他从句> ; QUIT; 上述从句的执行顺序如下: 1)根据FROM从句内的表生成卡氏积,即表1和表2的卡氏积。 2)根据WHERE从句选择卡氏积中的每一行进行扫描,判断该行是 否符合条件,删除不满足条件的行。 3)若SQL从句中有汇总函数,则根据汇总函数进行相应的处理; 否则,进行下一步。 4)输出满足条件的行。 使用PROC SQL对两表进行横向合并时,PROC SQL总是先生成卡 氏积表,再对卡氏积表中的行一一进行判断,确定其是否满足连接条 件。正因为如此,在使用SQL对表格合并时,并不要求表格已排序。但 这样做的缺点也是显然的:对两个相对较大的表横向合并时,中间会生 成一个更大的卡氏积表。 例6.14:表class包含了部分学生姓名、性别、年龄与身高,表 classfit包含了学生姓名和体重。利用SQL生成一个报表,仅输出在两个 表中都出现的行。 要实现此功能,SQL要对两个表进行检索,示例代码如下: data work.class; input name $ sex $ age height; datalines; Alice F 14 56.5 Carol F 14 62.8 James M 12 57.3 ; run; data work.classfit; input student_name $ weight; datalines; James 83 Carol 102.5 ; run; proc sql; title "Students Fitness "; select c.name, c.sex, c.age, c.height, cfit.weight from work.class as c, work.classfit as cfit where c.name = cfit.student_name ; quit; 在上述代码中,SQL首先生成了work.class与work.classfit的卡氏积 表,共6行,其中符合WHERE从句的仅有2行。最后按SELECT从句选 择的列输出,结果如图6.18所示。 图6.18 6.3.2 输出SELECT从句选择的列 使用SQL对表进行外连接 根据前面的介绍,内连接输出的是两表的卡氏积表中符合特定条件 的行。如果还希望同时输出一些不符合特定条件的行,那么可以考虑使 用外连接。对表A和表B进行外连接指的是输出两表内连接的行及部分 来自表A或者表B的行。外连接根据连接的方式可以分为以下3种: ·左连接(LEFT JOIN) ·右连接(RIGHT JOIN) ·全连接(FULL JOIN) 外连接的语法如下: PROC SQL; SELECT 表1.列1,表1.列2,…表2.列1,表2.列2,… FROM 表1 LEFT JOIN|RIGHT JOIN|FULL JOIN 表2 ON 连接条件 <其他从句> ; 注意 在上述各种外连接中,连接条件的关键字是ON,而不是 WHERE。 表6.3总结了上述3种不同的外连接方式及它们的含义。 表6.3 外连接方式及其含义 例6.15:使用SQL语句对表进行内连接,表A与表B如图6.19所示。 图6.19 表A与表B 以下代码分别使用上述3种连接条件对表A和表B进行连接。 proc sql; select * from work.A 连接条件 work.B on A.x = B.x; quit; 输出结果如图6.20所示。 图6.20 注意 3种连接的输出结果 比较一下使用全连接和使用卡氏积连接两个表的情况:表 A与表B的卡氏积表共有9行,而它们的全连接表只有5行。 使用SQL对表进行纵向合并 6.4 本节介绍如何利用SQL对表进行纵向合并。同上节一样,本节仅考 虑对两个表的纵向合并。用SQL进行表合并的语法如下: PROC SQL; SELECT* FROM A <其他从句> 连接方式<ALL><CORR> SELECT * FROM B <其他从句> ; 其中: ·上述每个SELECT的用法与其在单表中的用法一样,即可在 SELECT从句中添加其他从句,如WHERE、GROUP BY、HAVING等。 ·可供选择的连接方式有EXCEPT、INTERSECT、UNION和OUTER UNION等几种。 下面将具体讲解如何使用上述不同连接方式对表格进行纵向合并。 6.4.1 使用关键字EXCEPT对表进行纵向合并 使用关键字EXCEPT对表A和表B进行纵向合并,其结果是:SQL会 选择在表A中但不在表B中的行,且A中重复的行不会出现在合并的结果 中。 本小节将使用如图6.21所示的两个表。 例6.16:将上述表A和表B使用EXCEPT纵向合并,输出结果并分 析。 示例代码如下: proc sql; title "Combining Two Tables Vertically Using EXCEPT"; select * from A except select * from B ; quit; 用户提交上述代码,运行后,结果如图6.22所示。 图6.21 将使用的表A和表B 图6.22 运行结果 下面来具体分析EXCEPT合并表的具体过程: 1)EXCEPT对表的合并是根据表中列的位置来进行的。合并后的 表的列名称与第一个表的名称一致。合并时,要求两个表对应列的类型 必须一致,否则PROC SQL停止合并,并在日志中输出错误信息。在例 6.16中,表A与表B对应列的类型一致,合并后的表名与表A的名称一 致,即ID和X。 2)PROC SQL对表进行了两轮扫描。第一轮扫描在合并前进行, 此时SQL会扫描表A中重复的行,并将其删除。在例6.16中,表A的第4 行在扫描过程中会被删除(和第1行重复)。该步结束后,待合并的表 里还有4行。 3)SQL进行第二次扫描,关键字EXCEPT的作用是使SQL仅输出在 表A、不在表B中的行。上述4行中的(1,b)和(2,b)在表B中出 现,因此不输出。剩下的2行为最终输出结果,如图6.22所示。 1.关键字ALL 上例中,若用户希望输出所有在表A中但不在表B中的行(包括表A 中重复的行),则可在EXCEPT后加上关键字ALL。该关键字的作用 是,SQL会跳过第一次扫描,直接进行第二次扫描,即上例中的第3 步。其代码如下: proc sql; title "Combining Two Tables Vertically Using EXCEPT ALL"; select * from A except all select * from B ; quit; 输出结果如图6.23所示。 2.关键字CORR 上述例子中,对表的合并是基于表的位置的:只要是对应列的类型 相同(同为数值型或者字符型),就加以合并,不管列名称。当然, PROC SQL也可以基于表中列的名称对表进行纵向合并,具体示例如 下。 例6.17:在EXCEPT纵向合并表中使用关键字CORR。 示例代码如下: proc sql; title "Combining Two Tables Vertically Using EXCEPT CORR"; select * from B except corr select * from A ; quit; 其输出结果如图6.24所示。 图6.23 图6.24 代码输出结果 例6.17的输出结果 使用CORR时,SQL会根据表中列的名称进行合并,删除所有不是 同时在两个表中的列。上例中,列ID在两表中均有出现,因此保留到输 出结果中;而X与Y两列分别只在一个表中,因此不输出这两列。 注意 使用CORR关键字对表进行合并时,仅要求表中的列具有 相同名称,而不要求它们的位置也相同。例如,假设表C含有两列:X 和ID,在与A合并时,输出结果也应包含ID和X两列。 若同时使用ALL和CORR关键字,那么PROC SQL会根据表中列的 名称进行纵向合并,输出结果中可包括重复的行。 例6.18:在EXCEPT纵向合并表中同时使用关键字CORR和ALL。 示例代码如下: proc sql; title "Combining Two Tables Vertically Using CORR ALL"; select * from B except corr all select * from A ; quit; 其输出结果如图6.25所示。 6.4.2 使用关键字INTERSECT对表进行纵向合并 使用关键字INTERSECT对表A和表B进行纵向合并的结果是:SQL 会选择表A中不重复的、同时在表B中的行。 例6.19:使用如图6.26所示的两个表,并采用关键字INTERSECT纵 向合并表。 图6.25 图6.26 例6.18的输出结果 示例中使用的表A和表B 示例代码如下: proc sql; select * from A 连接方式 select * from B ; quit; 其中,使用不同连接方式时相应的输出结果如图6.27所示。 图6.27 使用不同连接方式时的输出结果 与EXCEPT一样,除非使用关键字CORR,否则使用INTERSECT纵 向合并表也是基于表中列的相对位置(即只要对应列的类型相同即 可)。在图6.27中,连接方式1中,SQL实现删去表A中重复的行,输出 剩余的行与表B的交集;连接方式2中,不删除表A中的重复行,直接输 出两表的交集;连接方式3中,对表格的合并是基于名称的,表A与表B 共同名称的列只有ID,此外,All的使用使得合并前不删除表A中重复的 ID。 6.4.3 使用关键字UNION对表进行纵向合并 若用户想要对表A和表B进行纵向合并,同时又要输出在表A中或者 在表B中且不重复的所有行,这种情况下,可使用关键字UNION。其使 用语法如下: PROC SQL; SELECT * FROM 表A <其他从句> UNION <ALL> <CORR> SELECT * FROM 表B <其他从句> ; 使用UNION进行表的纵向合并是基于列的位置。 例6.20:使用关键字UNION进行表的纵向合并。 下述代码使用UNION纵向合并图6.26中的表A与表B: proc sql; title "Combining Two Tables Vertically Using UNION"; select * from A union select * from B; quit; 使用关键字UNION纵向合并表时,SQL首先会对表进行纵向合并、 排序,如图6.28中的左图所示。接着删除重复的行,该结果为最终 PROC SQL的输出结果,如图6.28中的右图所示。 若在UNION后面加上ALL,那么SQL既不会删除重复的行,也不会 对行进行排序。 例6.21:在UNION中使用关键字ALL,对表进行纵向合并。 示例代码如下: proc sql; title "Combining Two Tables Vertically Using UNION ALL"; select * from a union all select * from b; quit; 其输出结果如图6.29所示。 图6.28 图6.29 合并过程及输出结果 例6.21的输出结果 从输出结果中可以看出,PROC SQL既没有删除表中重复的行也没 有对表进行排序。 例6.22:在UNION中使用关键字CORR,对表进行纵向合并。 示例代码如下: proc sql; title "Combining Two Tables Vertically Using UNION CORR"; select * from A union corr select * from B; quit; ID是表A与表B唯一的共同列,由于使用关键字CORR,PROC SQL 将根据表的名称来合并列,因此SQL输出结果中仅含列ID。在纵向合并 后的两个表中,ID值1、2和3是所有不重复的行,因此,输出结果如图 6.30所示。 图6.30 例6.22的输出结果 例6.23:在UNION中同时使用ALL和CORR,对表A和表B进行纵向 合并。 示例代码如下所示: proc sql; title "Combining Two Tables Vertically Using UNION ALL CORR"; select * from A union all corr select * from B; quit; 当同时使用ALL和CORR时,SQL会根据表中列的名称同时输出所 有的行(包含重复的行)。输出结果如图6.31所示。 图6.31 6.4.4 例6.23的输出结果 使用关键字OUTER UNION对表进行纵向合并 前面介绍的3种使用PROC SQL对表进行纵向合并的方法都会覆盖 表B的某些列。若用户想要在输出结果中同时查看来自表A和表B的所有 列,可以使用OUTER UNION进行合并。其使用语法如下: PROC SQL; SELECT * FROM 表A <其他从句> OUTER UNION <CORR> SELECT * FROM 表B <其他从句> ; QUIT; 使用OUTER UNION纵向合并表时具有如下特点: ·不会覆盖表的列。 ·两个表里的所有行都会出现在输出结果中。 注意 OUTER UNION不能和ALL同时使用。 考虑使用如图6.32所示的表A与表B。 图6.32 即将使用的表A与表B 例6.24:分别使用OUTER UNION与OUTER UNION CORR对表A和 表B进行纵向合并。 示例代码如下所示: proc sql; title "表1"; footnote "连接方式:outer union" ; select * from a outer union select * from b; quit; proc sql; title "表2"; footnote "连接方式:outer union corr" ; select * from a outer union corr select * from b; quit; 其输出结果如图6.33所示。 图6.33 例6.24的输出结果 从上述输出结果中可以看出,使用OUTER UNION纵向合并表不会 删除表中重复的行。事实上,PROC SQL在执行OUTER UNION命令时 只进行了一次扫描(比较:INTERSECT执行时进行了两次扫描,第一 次扫描时删去了重复的行)。另外,表1显示,OUTER UNION不会覆 盖列,来自于表A和表B的所有列在结果中都输出了。在表2中,由于使 用了关键字CORR,因此PROC SQL合并了两表中的ID列。 6.5 使用SQL管理表 本节将阐述如何利用SQL对表进行管理,包括创建新表、新增或者 删除若干行、更新表中列的值以及删除表等。对表进行管理时,往往需 要了解原表的结构,包括表中的列以及它们的属性。在这种情况下,用 户可以使用DESCRIBE语句,其使用语法如下: PROC SQL; DESCRIBE TABLE 表名; QUIT; SAS在日志中输出表的结构包括:表的列、属性、长度和标签。 例6.25:使用PROC SQL查看表sashelp.class的结构。 示例代码如下: proc sql; describe table sashelp.class; quit; 在日志中可以看到如图6.34所示的输出结果。 图6.34 表sashelp.class的结构 从上述输出结果中可以看到表sashelp.class的结构。例如,列Name 是一个长度为12的字符列,标签为“姓名”;列Age是数值型,标签为“年 龄”。 6.5.1 使用SQL复制、创建与删除表 本小节介绍如何利用SQL进行表的操作,具体包括复制表、创建一 个空表、根据原有表格创建一个结构类似的表、删除表。 1.复制表 在DATA步中,我们可以很方便地复制一个表。比如,以下代码将 生成一个和sashelp.cars一模一样的表,名为cars_copy。 data work.cars_copy; set sashelp.cars; run; 上述命令也可以用PROC SQL来实现,其代码如下: proc sql; create table work.cars_copy as select * from sashelp.cars; quit; 若用户仅需要表sashelp.cars中的部分列,可以在select语句中加以指 明。此外,PROC SQL不仅能复制表,更重要的是能根据原表灵活地创 建空表。 2.创建空表 例6.26:使用PROC SQL创建一个空表,表中的列以及它们的属性 要和表sashelp.new_class中对应列的属性一致。 示例代码如下: proc sql; create table new_class ( Name char(12) label='姓名', Sex char(4) label='性别', Age num label='年龄', Height num label='身高(英寸)', Weight num label='体重(磅)' ) ; quit; 上述代码创建了一个包含5列的空表,例如:列Name是字符型,括 号里面的数字指定了该列中字符的长度为12,标签为“姓名”。用户也可 以在定义列的时候指定该列的格式,例如,我们可以修改上述Height的 定义,指定其格式为comma8.1。 Height num label='身高(英寸)' format = comma8.1, 除了一一输入所有要创建的列的属性外,PROC SQL还提供了利用 关键字like来创建一个与已有表结构一样的空表。例如以下代码实现例 6.26中一样的任务。 proc sql; create table work.new_class like sashelp.class; quit; 在日志中显示了创建表的信息,如图6.35所示。 3.删除表 使用SQL删除表的操作是通过DROP TABLE语句来实现的。其使用 语法如下: PROC SQL; DROP TABLE 表名; QUIT; 其中,表名可以是“库名.表名”,也可以是用引号括起来的数据集文 件所在的物理路径。 例6.27:使用SQL中的DROP语句删除例6.26中生成的空表 work.new_class。 示例代码如下: proc sql; drop table work.new_class; quit; 提交上述代码,日志中显示了如图6.36所示的信息。 图6.35 图6.36 6.5.2 创建新表 删除表 使用SQL插入行 在PROC SQL语言中,用户可以使用INSERT把新的行值插入表 中,这里的表可以是空表。使用INSERT的方法有以下3种: ·SET从句 ·VALUE从句 ·使用查询的结果 在上述3种方法中,PROC SQL总是先在表中插入一个新行,然后 再对行进行赋值。若操作成功,SAS在日志中会显示相关的信息。 例6.28:在例6.26中生成的空表new_class中,使用SET语句插入下 面两个新行。(Meaghan,F,19,56,140),(Jack,M,21,63, 160)。 示例代码如下: proc sql; title "New_class"; insert into new_class set Name = 'Meaghan', Sex = 'F', Age=19, Height = 56, Weight = 140 set Name = 'Jack', Sex = 'M', Age=21, Height = 63, Weight = 160; title "Insert New Observations Using SET"; select * from new_class; quit; 在上述代码中,INSERT INTO标明了待操作的表,两个SET语句分 别对应两个新行。最后,SELECT语句选择插入两个新行后的表作为报 表的输出,如图6.37所示。 例6.29:使用VALUE语句实现例6.28中的任务,即插入新行。 示例代码如下: proc sql; insert into new_class (Name, Sex, Age, Height, Weight) values ('Meaghan', 'F', 19, 56, 140) values ('Jack', 'M', 21, 63, 160); title "Insert New Observations Using VALUE"; select * from new_class; quit; 在上述代码中,INSERT语句中的new_class标明了操作的表,括号 是表中待操作的列。若要插入的行包含了原表中的所有列,则上述括号 可以省略。每一个VALUE语句对应一个待插入的行,VALUE括号里面 值的顺序应该和INSERT括号中的列一一对应。输出结果如图6.38所 示。 图6.37 插入两个新行后的表 图6.38 用VALUE语句实现插入 最后,介绍如何将查询的结果作为新的行插入一个表。 例6.30:表sashelp.calssfit共10列,其中5列和class里面的列相同, 这5列分别是:Name、Sex、Age、Height、Weight。现在要求将 sashelp.calssfit中所有Age=12的行插入例6.29中的表new_class中(该表非 空,已经包含2行)。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. proc sql; insert into new_class select Name, Sex, Age, Height, Weight from sashelp.classfit where Age=12; title"Insert New Observations Using Query Results"; select * from new_class; quit; 上述代码中,第3~5行是一个标准的SELECT语句,该语句从表 sashelp.classfit中选出Age=12的行,且仅选择了5列。INSERT语句将 SELECT语句的结果作为新行插入表new_class中。第7行的SELECT语句 选择了表new_class中的所有内容作为报表输出,结果如图6.39所示。 图6.39 例6.30的输出结果 上述结果中的后5行是来自于表sashelp.classfit中满足条件Age=12的 所有行。 6.5.3 使用SQL删除部分行 在SQL中用户可以使用DELETE语句删除部分行。若该语句成功执 行,SAS在日志中输出相应的信息。DELETE语句的语法如下: DELETE FROM 表名 <WHERE语句> 例6.31:复制表sashelp.class为work.class,计算表work.class中体重 和身高的比,删去所有比值大于1.5的行。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. proc sql; create table class as select * from sashelp.class; quit; proc sql; delete from class where (class.weight/class.height)>1.5; quit; 在日志中,我们可以看到如图6.40所示的信息。 图6.40 删除部分行 该信息表明,删除操作成功。 6.5.4 使用SQL修改表的列 在6.2.2节中介绍了如何使用SQL对列进行一些操作,包括新增列、 在输出报表中修改列的格式等,这些都不会影响原表。本节介绍的修改 表列的操作是对原表直接进行的操作。使用SQL对原表的列进行删除、 新增或者修改属性的语句是ALTER TABLE,其语法如下: PROC SQL; ALTER TABLE 表名 ADD 列1,列2, … DROP列1,列2, … MODIFY列1,列2, …; QUIT; 其中,关键词ADD、DROP、MODIFY分别对应着指定列的新增、 删除和修改属性这3种不同的操作。上述3种操作在ALTER语句中至少 需要出现一种。此外,三者之间无顺序上的先后关系。 例6.32:使用SQL修改表work.class中的列,删除列Age与Sex;新增 两个列Student_ID与Boarding,其中列Student_ID为数值型,其格式 为“4.”,Boarding的格式为字符型,长度为1;修改列Height格式 为“8.1”,标签为“ModifiedHeight”。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. proc sql; create table work.class as select * from sashelp.class; quit; proc sql; alter table work.class add Student_ID num format = 4., Boarding char(1) drop Age, Sex modify Height format = 8.1 label = 'ModifiedHeight'; quit; 操作前后数据集的对比如图6.41和图6.42所示。 6.5.5 图6.41 操作前的数据集 图6.42 操作后的数据集 使用SQL更新列的值 在实际应用中,常常需要对列值进行更新,而且对于不同行,其更 新的规则可能是不一样的。例如,表sashelp.class中学生的身高 (Height)与体重(Weight)在下一年中将会发生变化,据预测,年龄 (Age)在11~12岁的学生,身高和体重会增长5%,13~14岁的学生,身 高和体重会有6%的增长,其余年龄的学生其身高和体重会有4%的增 长。在这种情况下,不同行的Height和Weight的更新规则将会不一样, 对此,PROC SQL提供了以下两种处理的方法: ·使用多个UPDATE语句。 ·使用UPDATE和CASE语句。 例6.33:使用多个UPDATE语句对表sashelp.class中列年龄、身高和 体重进行更新。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. proc sql; create table class as select * from sashelp.class ; quit; proc sql; update class set height = height*1.05, weight = weight *1.05 where age in (11, 12); update class set height = height*1.06, weight = weight *1.05 where age in (13, 14); update class set height = height*1.04, weight = weight *1.05 where age in (15, 16); update class set age = age+1; quit; proc sql outobs=6; title "更新前后对比(1)"; select class.* ,c.Age as age_before label= '原年龄', c.Height as h_before label = '原身高', c.Weight as w_before label = '原体重' from class, sashelp.class as c where class.name = c.name; quit; 上述代码中,第1~4行,复制sashelp.class表为class(库sashelp里面 的数据集不允许更改,因此要把数据集复制到work库里面操作);第 6~8行、第9~11行和第11~14行分3个年龄段对表class中的Height与 Weight值进行了相应的更新;第15~16行将上述更新完的表中的Age值加 1;第18~24行输出原来表和更新后表的前6行,结果如图6.43所示。 图6.43 更新前后对比(1) 例6.34:使用UPDATE和CASE语句对sashelp.class中的年龄、身高 和体重进行更新。 示例代码如下: 1. proc sql; 2. create table class as 3. select * from sashelp.class ; 4. quit; 5. proc sql; 6. update class 7. set height = height* 8. case 9. when age in (11, 12) then 1.05 10. when age in (13, 14) then 1.06 11. else 1.04 12. end; 13. 14. update class 15. set weight = weight* 16. case 17. when age in (11, 12) then 1.05 18. when age in (13, 14) then 1.06 19. else 1.04 20. end; 21. update class 22. set age = age+1; 23. quit; 24. 25. proc sql outobs=6; 26. title "更新前后对比(2)"; 27. select class.* ,c.Age as age_before label= '原年龄', 28. c.Height as h_before label = '原身高', 29. c.Weight as w_before label = '原体重' 30. from class, sashelp.class as c 31. where class.name = c.name; 32. quit; 上述代码中,第1~4行复制了表sashelp.class;第7~12行使用单个 UPDATE和CASE对列Height进行了更新(因为原表中除了11~14岁外, 只有15~16岁,因此可以使用ELSE语句);第14~20行使用单个 UPDATE和CASE对列Weight进行更新;第21~22行对Age进行更新;第 26~31输出前后对比结果,如图6.44所示。 图6.44 注意 更新前后对比(2) 上述两个例子中对Age的更新应该是在最后进行,因为之 前列Height和Weight的更新是基于原来表中Age值的。 6.6 本章小结 本章介绍了使用SAS SQL语言的基本结构和使用SQL过程检索数据 的基本语法;介绍了如何使用SQL过程对多个表进行横向合并和纵向合 并,以及在合并表的过程中多个关键字的使用方法和区别;在最后一部 分介绍了如何使用SQL过程管理表,包括复制表、删除表、创建空表、 在表中插入行、删除行、修改表中的列以及更新列的值等内容。 第7章 SAS宏语言 SAS宏语言是SAS编程的一个重要组成部分。使用SAS宏语言可以 实现代码的重复利用、完成复杂的逻辑判断和条件控制,从而使代码更 为简短清晰。此外,使用宏语言可以自动生成代码,这使得程序具备了 极高的灵活性且易于维护。本章将介绍宏变量的定义与使用、宏函数、 宏的开发,以及宏语言与之前学过的DATA步、PROC步和SQL语言的 交互,同时也会介绍如何自动生成代码和条件控制等。 7.1 SAS宏语言概述 SAS宏语言实际上是一种文本语言。在宏语言中所有变量的值都只 能是字符。宏语言的特征之一是带有符号“&”和“%”,前者一般与宏变 量一起使用,后者常与宏一起使用。下面结合例子简单说明如何使用宏 语言来提高编程的效率。 例7.1:数据集sashelp.orsales中包含1999~2002年的销售信息。要求 生成一个2001年销售记录的报告,并且要求报告的标题必须包含报告创 建的时间、星期与日期,副标题要包含生成报告的操作系统与SAS版本 信息。 实现代码如下: data orsales2001; set sashelp.orsales; if year = 2001; run; proc print data = orsales2001; title "Sales Record in Year2001"; footnote1 "Created 22:01Monday, 21OCT2013"; footnote2 "on the WIN System Using SAS 9.3"; run; 上述代码虽然实现了目标,但是不易维护,主要体现在以下两个方 面: ·阴影部分(报告时间、星期、日期以及操作系统信息等)都需要 用户手动输入。 ·上述代码中2001出现了4次。假设用户想查看其他年份的数据集, 不使用宏语言,只能在程序中将出现的所有2001都做替换。 使用宏语言改写上述代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. %let year = 2001; data orsales&year; set sashelp.orsales; if year = &year; run; proc print data = orsales&year; Title "Sales Record in Year&year"; footnote1 "Created &systime &sysday, &sysdate9"; footnote2 "on the &sysscp System Using SAS &sysver"; run; 上述代码中,第1行定义了宏变量year,并赋值为2001。在余下代 码中,使用&year来代替2001。假设现在要查看2002年的信息,只需修 改第1行赋值处即可,这显然比例7.1中的代码更容易维护。此外,脚注 footnote里面利用&systime、&sysday等系统宏变量,自动生成了需要的 时间信息,避免了手工输入(具体见例7.7)。 宏语言的作用还体现在可以有条件地执行代码。假设一位数据管理 人员需要在每天下班前生成一个当天的日报告,若该天是周五,则需要 生成一个本周的报告(周五不用生成日报告)。这就涉及条件控制了。 使用DATA步和PROC步都无法简单地实现流程的条件控制,但通过宏 语言可以很方便地解决这一问题。这里仅给给出流程,具体代码会在介 绍相关知识后给出。流程如下: %if (当天是周五) %then (周报告代码) %else (当天报告代码) 运行上述程序,SAS会自动判断该执行哪段代码。总之,SAS宏语 言可以让SAS编程更加灵活,易于使用与维护。 宏变量 7.2 7.2.1 宏变量的定义 掌握宏语言首先需要学习如何定义和使用宏变量。用户可以在SAS 程序中除数据行以外的任何地方定义宏变量。宏变量的定义方法主要有 以下3种: ·使用%LET语句。 ·在DATA步中定义宏变量。 ·在SQL语言中定义宏变量。 现在介绍如何使用%LET语句定义宏变量,后面会专门讲述如何在 DATA步和SQL语句中定义宏变量。%LET语句的使用语法如下: %LET 变量名 = 宏变量值; 其中: ·宏变量名最多可以包含32个字符。 ·宏变量名必须由字母和下划线开始,且由字母、下划线以及数字 组成。 ·宏变量名不能包含空格。 ·宏变量名区分大小写。 ·宏变量值可以是任何字符串,但是长度不能超过65534。 使用宏变量要注意以下几点: ·宏变量值的内容均存储为字符串。 ·宏变量值若含有数学表达式,数学表达式不会被计算。 ·宏变量值的大小写不转换。 ·若宏变量值头尾含有空格,那么赋值时头尾的空格会被移除。 ·如果宏变量值包含引号,那么赋值时引号也会被储存。 例7.2:%LET语句的使用。表7.1列举了不同情形下%LET语句的用 法以及对应的输出。 表7.1 %LET语句的用法 7.2.2 宏变量的调用 1.宏变量的直接调用 在程序中为了得到宏变量的值,需要先引用该宏变量。具体的规则 如下: &宏变量名 宏变量的引用过程也称为宏变量的解析。要注意的是,单引号内的 宏变量不会被解析,所以,如果要使用引号并且希望引号中的宏变量被 解析,则必须使用双引号,具体见例7.3。 例7.3:在代码中定义宏变量town,在第二个和第三个DATA步中分 别解析该变量。 示例代码如下: %let town = Detroit; data work.city; input city $ state $; datalines; Detroit MI Chicago IL ; run; data work.city2; set work.city; where city = '&town'; run; data work.city3; set work.city; where city = "&town"; run; SAS日志显示第三个DATA步成功解析宏变量Town,而第二个 DATA步里宏变量Town没有被解析,如图7.1所示。 图7.1 宏变量Town的解析 2.宏变量的间接调用 在宏编程中,有时需要间接地调用宏变量,间接调用宏变量是通过 多个“&”符号来实现的。宏处理器对多个“&”的处理规则是:从左到右 进行扫描,若宏变量前仅含有一个“&”,则解析该宏变量;否则将相邻 的两个“&”替换成一个“&”,重复上述扫描过程。 例7.4:多个“&”符号的使用,在%PUT语句中间接引用宏变量 Name9。 示例代码如下: %let CustID = 9; %let Name9 = Kirsty; %put &&&Name&CustID; 在上述代码中,%PUT语句进行的扫描如图7.2所示。 图7.2 对“&”符号的扫描 3.宏变量在文本中的分隔 宏变量可以是任何文本,编程中宏变量往往需要和其他文本结合在 一起,在这种情况下,就要让宏变量与其他文本有所分隔。分隔的方法 是在宏变量名后加上一个“.”号,也就是说,符号“&”与“.”之间的部分为 要解析的变量名。 例7.5:数据集sashelp.orsales中变量quarter的值包含年份与季度信 息,如1999Q1代表年份为1999,季度为第1季度。现在要根据季度值的 不同,创建4个数据集,使得季度值相同的观测在同一个数据集内。 此时,需要使用“.”让宏变量与其他文本分隔开来。示例代码如 下: %let name = SaleQ; data work.&name.1 work.&name.2 work.&name.3 work.&name.4; set sashelp.orsales; if (substr(quarter,5, 2) = 'Q1' ) then output work.&name.1; else if (substr(quarter,5, 2) = 'Q2' ) then output work.&name.2; else if (substr(quarter,5, 2) ='Q3') then output work.&name.3; else output work.&name.4; run; 在上述代码中,name前后的点号起着分隔宏变量与文本的作用,是 必不可少的。执行上述代码,生成4个数据集,如图7.3所示。 图7.3 生成4个数据集 7.2.3 宏变量的查看 前面介绍了宏变量的定义与引用,在SAS程序调试过程中,用户常 常需要查看宏变量的值。常见的查看宏变量值的方法有以下两种: ·%PUT语句。 ·宏语言系统选项OPTIONS SYMBOLGEN。 %PUT语句的语法如下: %PUT &宏变量名; 使用%PUT输出宏变量时,可以附加一些说明,让输出结果一目了 然。例如,假设用户需要查看宏变量year中存储的值,则可以通过以下 代码实现: %let year = 2001; %put year = &year; 在日志里,用户可以查看到如图7.4所示的输出结果。 图7.4 宏变量的输出 注意 PUT和%PUT不能混淆:前者仅在DATA步中使用;后者 属于宏语言范畴,可以在除了数据行以外的任何地方调用。 此外,SAS为%PUT提供了一些选项,语法如下: %PUT 选项; 其中,选项_ALL_用于输出所有宏变量的值,包括系统宏变量和用 户自定义宏变量;选项_AUTOMATIC_仅输出系统宏变量;选项 _USER_仅输出用户定义宏变量。所谓的系统宏变量,顾名思义就是由 SAS系统生成的宏变量,而用户定义宏变量则指用户自己定义的宏变 量。 除了使用%PUT语句在日志里查看宏变量的值以外,用户还可以通 用修改SAS宏语言系统选项来查看宏变量的值。具体而言,在使用选项 SYMBOLGEN时,如果用户解析某个宏变量,SAS则会在日志中显示该 宏变量的解析值(默认情况下不显示该信息)。 SYMBOLGEN选项的语法如下: OPTIONS NONSYMBOLGEN|SYMBOLGEN 选项SYSBOLGEN为系统选项,这就表示该选项会一直有效,直至 用户修改该选项或者SAS会话结束。 例7.6:使用SYMBOLGEN选项,定义并调用宏变量Year。 示例代码如下: OPTIONS SYMBOLGEN; %let Year = 2003; data _null_; Current_year = &Year; run; 在日志中显示的信息如图7.5所示。 图7.5 7.2.4 显示宏变量的解析值 宏变量的分类 正如上节指出,宏变量按来源可分为系统宏变量和用户自定义宏变 量。此外,按可使用的范围,宏变量可分为局部宏变量与全局宏变量。 SAS系统在启动时就自动生成了一些宏变量。表7.2列出了一些常见 的SAS系统宏变量及它们的含义。 表7.2 常见的系统宏变量及其含义 例7.7:在7.1节中,使用宏变量重新编写了例7.1中的程序,提交该 代码,运行结果中,标题部分如图7.6所示。 图7.6 使用系统宏变量自动生成标题 脚注部分如图7.7所示。 图7.7 7.2.5 使用系统宏变量自动生成脚注 宏变量的删除 前面已经介绍了如何定义、调用与查看宏变量的值,在编程中有时 需要删除某些自定义的宏变量(系统宏变量是不允许删除的)。删除用 户自定义的宏变量是通过%SYMDEL来实现的,其语法如下: %SYMDEL 宏变量名1宏变量名2; 例如,以下代码可删除例7.7中定义的宏变量year。 %symdel year; 宏函数 7.3 SAS提供了大量的函数供用户在DATA步中调用。SAS在宏语言中 也定义了一些函数,这些函数称为宏函数。SAS函数和宏函数的区别在 于,宏语言函数一般前面带有宏符号“%”。例如,下面代码中, 带“%”的SUBSTR标明该函数属于宏范畴。 substr(date, 6); %substr(&sysdate9, 6); 本节介绍以下3类宏函数: ·在宏语言中调用SAS函数 ·处理算术与逻辑表达式的宏函数 ·处理文本的宏函数 7.3.1 在宏语言中调用SAS函数 SAS通过宏函数%SYSFUNC来支持绝大多数SAS函数在宏语言中的 调用,其语法如下: %SYSFUNC(函数(函数参数)<,格式>) 其中: ·函数是使用的SAS函数名称。 ·函数参数是该SAS函数使用时所需要的参数。 ·格式是用户根据需要选择输出结果的格式。 例如,以下代码在宏语言的环境中会调用SAS函数today(): Title "Report produced on %sysfunc(today(), worddate.)"; 所有的SAS函数都可以和%SYSFUNC一起使用,但不包括DIF、 DIM、HBOUND、INPUT、IORCMSG、LAG、LBOUND、MISSING、 PUT、RESOLVE、SYMGET和所有的变量信息函数(Variable Information Function)。有关这些函数的详细信息可参见SAS帮助文 档。 7.3.2 用宏函数处理算术与逻辑表达式 在例7.2中,使用%LET语句定义了宏变量x并赋值为“3+4”,这 里“+”被当成字符存储。若用户希望SAS执行算术符号“+”,可以使用宏 函数%EVAL。宏函数%EVAL的作用如下: ·将表示整数(和十六进制数)的字符串转为整数。 ·进行算术和逻辑运算。 ·将代表算术、比较与逻辑的符号转化成为宏范畴的符号。 对于一个整数型的算术表达式,若其结果不是整数,%EVAL会将 结果的小数部分截断,输出其整数部分作为结果。此外,用%EVAL进 行算术运算的结果仍然为字符型。用%EVAL进行逻辑运算的结果为 1(真)或者0(假)。下面的代码举例说明如何使用%EVAL进行算术 与逻辑运算。 例7.8:宏函数%EVAL的使用。 示例代码如下: %put %put %put %put eval(2+2) = %eval(2+2); eval(7/4) = %eval(7/4); eval(10 gt 2) = %eval(10 gt 2); eval(2+2.1) = %eval(2+2.1); 上述第2个%EVAL函数中算术表达式的结果不是整数,因此输出其 整数部分作为结果;第3个%EVAL函数为逻辑表达式,输出结果为 1(真);最后一个表达式不是算术表达式,因此不适用%EVAL函数。 日志中的具体信息如图7.8所示。 图7.8 宏函数%EVAL的使用 日志中%EVAL函数不计算表达式“2+2.1”,解决的办法是使 用%SYSEVALF函数。%SYSEVALF函数是唯一能处理包含浮点或者是 缺失值的宏函数,其使用语法如下: %SYSEVALF(表达式<, 转换格式>) 其中: ·表达式是算术或者逻辑表达式。 ·转换格式是函数选项。加上转换格式,用户可以对%SYSEVALF 返回的结果进一步转化,常见的转换格式有布尔型(BOOLEAN)、取 整(INTEGER)、取上整(CEIL)和取下整(FLOOR)。 例7.9:在%SYSEVALF中指定不同的函数选项进行计算。 示例代码如下: %let a = 2; %let b = 2.1; %put %put %put %put %put The result with SYSEVALF is: %sysevalf(&a + &b); BOOLEAN conversion: %sysevalf(&a + &b, boolean); INTEGER conversion: %sysevalf(&a + &b, integer); CEIL conversion: %sysevalf(&a +&b, ceil); FLOOR conversion: %sysevalf(&a +&b, floor); 在日志中输出结果如图7.9所示。 图7.9 7.3.3 函数%SYSEVALF及其选项 常见的处理文本的宏函数 现在介绍几个常见的用于文本处理的宏函数。 %SUBSTR函数用于从一个字符串中选取指定的部分子字符,其用 法如下: %SUBSTR(变量, 开始位置 <,长度>) 其中: ·变量可以是一个字符串,可以通过宏变量直接定义得到,也可以 通过表达式的运算得出。 ·开始位置是要提取的子字符串在原始字符串中的起始位置,其值 应该是一个正整数,可以是某些函数与表达式的运算结果。 ·长度是一个可选项,其值也是一个正整数。同样,这个整数可以 是某些函数与表达式的运算结果,代表要选取的子字符串的长度。当该 长度值缺省时,SAS会截取从指定位置的字符开始至结束之间的字符 串;如果长度大于从指定位置到结束的字符总数,那么生成的结果虽一 样,但SAS会在日志中生成警告信息。 文本中常常含有一些特殊符号与关键字,这需要做特殊处理。例 如“AND”在宏语言里是一个逻辑符号,如果不加以处理,则试图将“A AND B”赋值给一个宏变量是个语法错误。宏函数%STR可以用来处理类 似情况,其使用语法如下: %STR(文本) 其中,文本可包含以下符号与关键字: ; + - * / ,<> = blank LT EQ GT AND OR NOT LE GE NE 需要注意的是,若文本开始或结尾处包含空格,那么%STR函数不 会删去这些空格。 一般情况下,SAS认为引号与括号是成对出现的。因此,若文本中 需要含有不成对的引号或括号,则需要做特殊处理。处理方法之一是使 用%STR函数,并在文本中不成对的引号或者括号前加上一个百分号, 具体见例7.10。 例7.10:在不同的情形下调用%STR函数。 示例代码如下: %let %let %put %put text1 = %str(A AND B); text2 = %str(Joan%'s Report); text1:&text1; text2:&text2; 日志中显示的信息如图7.10所示。 在宏语言环境中,符号“&”表示宏解析的开始,与此同时,“&”也 是文本中常见的符号之一。因此,若文本中含有“&”,则需要特殊处 理。这种情况下,可以使用%NRSTR函数(NR是单词NOT RESOLVE 的缩写)。 例7.11:宏函数%STR与%NRSTR的比较。 示例代码如下: %let Period = %str(Jan&Feb); %put Period resolved to :&Period; %let Period = %nrstr(Jan&Feb); %put Peorid resolved to :&Period; 日志中显示的信息如图7.11所示。 图7.10 图7.11 %STR函数的使用 对比函数%STR与%NRSTR 可以看出,在语句“%let Period=%str(Jan&Feb);”中,SAS认 为“&”后面的字符串“Feb”是一个变量,试图解析该变量,从而产生警告 信息。使用函数%NRSTR后,“&”符号不会被解析,宏函数Period被赋 值为“Jan&Feb”。 表7.3列举了其他一些处理字符的宏函数,具体语法选项可以查阅 SAS帮助文档。 表7.3 常见的用于处理字符的宏函数 宏 7.4 本节介绍宏语言的另外一个重要组成部分:宏程序,简称宏 (macro)。利用宏,我们可以实现代码的结构化和重复使用。 7.4.1 宏的定义与调用 一个宏是指以%MACRO开始,以%MEND结束的一段宏文本,其 使用语法如下: %MACRO宏名称 宏文本 %MEND <宏名称>; 其中,宏文本可以是: ·任何文字。 ·SAS语句、表达式。 ·整段SAS程序,如DATA步和SQL程序。 ·上述三者的混合。 需要指出的是,%MEND后面的宏名称是可选的,但是分号是必须 有的。此外,宏名称的命名规则遵循SAS变量的命名规则,一些关键 字、词不能使用,比如,LEFT、SCAN、IF...THEN及ELSE等。一般情 况下,用户可以根据宏的作用来命名,这样更直观、易懂,也不易与关 键字、词重复。 宏定义完后,就可以被调用了。调用方法如下: %宏名称 调用宏时,宏名称后不需要加分号。此外,宏可以在SAS程序中除 数据行以外的任何地方被调用。例如,假设一个程序中多处要用到当前 系统时间,则可以考虑写一个调用时间的宏。代码如下: %macro time; %put The current time is %sysfunc(time(),timeampm.); %mend time; 其他程序代码片段1 %time 其他程序代码片段2 %time … 对于那些仅在宏内部有效和可以使用的变量,我们称之为局部宏变 量;反之,可以在程序中除数据行以外的任何地方引用的宏变量则称为 全局宏变量。 7.4.2 宏的存储 1.宏的编译 用户在提交一个宏的定义,且经宏处理器编译无误后,SAS会加以 存储。用户可以使用系统选项MCOMPILENOTE在日志中显示编译信 息,其使用语法如下: OPTIONS MCOMPILENOTE = ALL|NONE; 其中,系统默认的值为NONE。 例7.12:使用选项MCOMPILENOTE重新提交宏TIME的定义。 示例代码如下: OPTIONS MCOMPILENOTE = ALL; %macro time; %put The current time is %sysfunc(time(),timeampm.); %mend time; 在日志中显示的信息如图7.12所示。 图7.12 MCOMPILENOTE选项的使用 2.宏的存储 在上一节中,定义了一个叫time的宏。经过编译、运行后,在SAS EXPLORER下的Work逻辑库里面,用户可以看到一个名为Sasmacr的文 件夹。双击该文件夹,可以看到一个名为Time的文件,这正是我们刚刚 定义的宏time,如图7.13所示。 图7.13 定义的宏time 默认情况下,用户定义的所有的宏都会被保存在Work逻辑库里的 Sasmacr内。由于在SAS退出时Work逻辑库里的文件都会被删除,因 此,如果希望将宏永久保存以便将来使用,则需要将宏存储到Work逻 辑库以外的地方。 宏的存储一般要经过以下步骤: 1)指定或者定义一个SAS库,用来存储宏。 2)设置系统选项MSTORED。 3)定义宏的同时设定存储选项。 宏的储存的具体语法如下: OPTIONS MSTORED SASMSTORE = 用户指定逻辑库库名称; %MACRO 宏名称/STORE SOURCE; 宏具体内容 %MEND 宏名称; 其中: ·MSTORED用于开启宏存储的系统选项。 ·SASMSTORE指定宏保存的库。 ·STORE SOURCE表示定义的同时存储该宏。 以下代码定义了逻辑库store,在定义宏time的同时,会把该宏保存 在store库里。 libname store 'c:\ch7'; options mstored sasmstore = store; %macro time/store source; %put The current time is %sysfunc(time(),timeampm.); %mend time; 提交上述代码,关闭SAS会话,用户可以在c:\ch7里看到一个名为 sasmacr.sas7bcat的文件,宏time已经被永久性保存。 3.调用已存储的宏 默认情况下,当用户调用一个宏时,SAS仅在Work逻辑库里的 Work.Sasmacr中进行搜索。因此,在调用已经保存的宏时,用户需要指 定该宏所在的逻辑库。例如,我们要在另外一个SAS会话里调用保存在 c:\ch7的宏,代码如下: libname store'c:\ch7'; options mstored sasmstore = store; %time 在上述代码中,首先把库store指向了操作系统中保存宏的具体物理 位置;接着,OPTIONS选项指定了搜索库为store,这样就可以调用宏 time了。 7.4.3 宏的参数 在7.3.3节中介绍了一些常见的处理文本的宏函数,这些函数都含有 输入参数。在宏语言中,用户可以自己定义宏函数,即带参数的宏。例 如,我们需要对一个数据集data1中的变量var1做分析,代码如下: proc means data = data1; var var1; run; 假设现在要对10个类似的数据集做类似的分析,这10个不同的数据 集中需要分析的变量名称也不一样。可想而知,该任务涉及上述代码的 重复使用,这时就可以考虑用宏语言来编程。由于每次代码中阴影部分 的内容都不同,每次调用宏calc时,都希望能够把阴影部分的内容(即 当前数据名和变量名)传递给宏calc。在这种情况下,就可以用带参数 的宏来实现。具体代码如下: %macro calc(dsn, vars); proc means data = &dsn; var &vars; run; %mend calc; 上述宏calc含有两个参数:dsn和vars。当SAS宏处理器编译到带参 数的宏时,会自动生成相应名称的局部宏变量。上例中生成的两个局部 宏变量分别是:dsn和vars。正因为如此,我们在PROC MEANS的内部 才可以解析这两个宏变量。 调用的过程如下: %let dsn = data1; %let vars= var1; %calc(data1, var1) 宏的参数可以分为以下3类: ·固定位置参数 ·关键词参数 ·混合型参数(即以上两种类型的混合) 1.宏中固定位置参数的定义与使用 宏中定义固定位置参数的方法如下: %MACRO 宏名称(参数1,参数2, …); 宏文本 %MEND <宏名称> 其中,多个参数之间用逗号隔开,每个参数名也应该符合SAS对变 量名的要求。参数列中提到的参数可以在宏文本中以宏变量的形式加以 引用。 例7.13:建立一个宏,对数据集sashelp.orsales中在既定时间段内的 订单数按照生产线(product_line)进行统计。以下的宏使用PROC FREQ实现这一任务,并会将table语句中的选项和时间段的开始与结束 作为宏的参数来调用。 示例代码如下: %macro count(opts, start, stop); proc freq data = sashelp.orsales; where year between &start and &stop; table product_line/&opts; title1 "Order Between&start and&stop "; run; %mend count; %count(nocum, 1999, 2000) 输出结果如图7.14所示。 图7.14 宏COUNT的输出结果 2.宏中关键词参数的定义与使用 关键词参数给每个参数都会确定一个名称和默认值,在宏中该其类 型的参数方法如下: %MACRO 宏名称(参数1=值1,参数2=值2, …); 宏文本 %MEND <宏名称> 其中,多个参数之间用逗号隔开,参数名的命名规则应该符合SAS 对变量名的要求,每个参数名后面用等号给出参数默认值(允许使用空 值)。参数列中提到的参数可以作为宏文本中的宏变量加以引用。 调用带关键词参数的宏的语法如下: %宏名称(参数1=值1,参数2=值2, …) 例7.14:将例7.13中宏的定位置参数改为带关键词参数。 示例代码如下: %macro count(opts= , start=1999, stop=2000); proc freq data = sashelp.orsales; where year between &start and &stop; table product_line/&opts; title1 "Order between &start and &stop"; run; %mend count; 以下程序调用了上述带关键词参数的宏: %count() %count(opts = nocumnopercent) %count(stop=2000, opts = nocum) 第一次调用宏count,不带任何参数,系统调用默认值,即:opts为 空、start值为“1999”、stop为“2000”。输出结果如图7.15所示。 第二次调用宏,只给定了一个参数的值,即opts的值,为 nocumnopercent。调用宏时,系统使用了该参数的值,而非默认值,对 于其余两个为给定的参数值,宏采用的默认值。输出结果如图7.16所 示。 第三次调用宏,仅给定了两个参数值,且修改了宏参数的位置,这 在带关键词参数的宏中是允许的,其输出结果如图7.17所示。 图7.15 使用不同参数时宏COUNT的输出结果(一) 图7.16 使用不同参数时宏COUNT的输出结果(二) 图7.17 使用不同参数时宏COUNT的输出结果(三) 3.宏中混合型参数的定义与使用 所谓的混合型参数指的是宏参数中既含有定位置型的参数,也含有 带关键词的参数,其语法如下: %MACRO 宏名称(参数1, 参数2, …,参数K,参数K+1=值1, 参数K+2=值2, 宏文本 %MEND <宏名称> …); 其中,参数名之间用逗号隔开,参数名的要求与SAS对变量名的要 求一致,所有定位置型参数(不带等号)必须在关键词参数(带等号) 之前。 例7.15:修改例7.14中的宏,使其成为带混合型参数的宏。 示例代码如下: %macro count(opts , start=1999, stop=2000); proc freq data = sashelp.orsales; where year between &start and &stop; table product_line/&opts; title1 "Order between &start and &stop"; run; %mend count; %count(2000, nocum, 2001) %count(nocum, start = 2000, stop = 2001) %count(, start = 2000, stop = 2001) 第一次调用该宏,出现了错误,原因在于调用该宏时,定位置型参 数opt的值在关键词参数的后面,日志信息如图7.18所示。 图7.18 带混合型参数宏COUNT的输出结果(一) 第二次调用时,调整了顺序,结果如图7.19所示。 第三次调用时,定位置型参数为空,输出结果如图7.20所示。 7.4.4 图7.19 带混合型参数宏COUNT的输出结果(二) 图7.20 带混合型参数宏COUNT的输出结果(三) 宏与宏变量 1.全局宏变量与局部宏变量 在7.4.1节末尾,提到了全局宏变量和局部宏变量的定义。前面介绍 的SAS系统自动生成的宏变量(如SYSDATE和SYSDAY等),均属于 全局宏变量,而例7.15中的opt、start、stop等则属于局部宏变量。 例7.16:定义一个宏变量i,并且试图在宏外部调用该变量。 示例代码如下: %macro NullMacro; %let i = 1; %put inside macro i = &i; %mend NullMacro; %NullMacro; %put outside macro i = &i; 日志中显示的信息如图7.21所示。 出错原因是宏变量i只存在于宏NullMacro内部。当然,用户也可以 显式地声明全局宏变量与局部宏变量,其使用语法如下: %GLOBAL 变量1 变量2 变量N; %LOCAL 变量1 变量2 变量N; 其中,变量可以是由字符表达式生成的。 例7.17:修改例7.16中宏变量i的定义,使其能在宏外部使用。 此处需要利用%GLOBAL语句来定义全局宏变量。示例代码如下: %macro NullMacro; %global i; %let i = 1; %put inside macro i = &i; %mend NullMacro; %NullMacro; %put outside macro i = &i; 日志中显示的信息如图7.22所示。 图7.21 图7.22 在宏外部调用局部宏变量 使用%GLOBAL定义全局宏变量 2.全局符号表与局部符号表 SAS的全局宏变量与局部宏变量分别存储于全局宏变量表(Global Symbol Tables)与局部宏变量表(Local Symbol Tables)中。全局宏变 量表创建于SAS启动时。需要注意的是,对每个宏,SAS都会生成一个 对应该宏的局部宏变量表。换而言之,系统可能包含多个局部变量表。 不同的局部宏变量表中很能含有相同名称的宏变量。 3.宏变量值的更新与解析 由于局部宏变量表和全局宏变量表可能含有相同名称的宏变量,因 此使用%LET语句对这些宏变量进行赋值时,就需要知道SAS对变量的 处理规则。以下分两种情形讨论处理规则:在宏外部使用%LET语句和 在宏内部使用%LET语句。 当宏处理器在宏定义的外部遇到创建(%LET语句)与调用语句 (&宏变量名)时,宏变量处理器仅检查全局宏变量表,并根据全局宏 变量表决定是创建、更新,还是解析。 当宏处理器在宏内部遇到一个%LET语句时,如以下语句: %let mvar = 值1 宏处理器按照以下顺序处理宏变量值: 1)检查局部宏变量表,看mvar是否存在于局部宏变量表中。如果 存在,更新其值,若不存在,进行下一步。 2)检查全局宏变量表,看mvar是否存在与全局宏变量表中。若存 在,更新其值,若不存在,进行下一步。 3)在局部宏变量表中创建宏变量并赋值。 类似的,当宏处理遇到如下调用宏变量的语句时: &mvar 宏处理器会先检查局部宏变量表,如果mvar在局部宏变量表中,则将该 值作为解析值;否则,宏处理器检查全局宏变量表,若mvar存在于全局 宏变量表中,则将表中该变量的值作为解析值,若不存在,宏处理器在 日志中生成该变量未被解析的警告信息。 例7.18:在代码中定义两个宏,outer和inner,且在第3行、第8行和 第10行分别使用%LET语句。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. %macro outer; %local x; %let x=1; %inner %mend outer; %macro inner; %local y ; %let y = &x; %mend inner ; %let x = 0; %outer; 下面来详细分析%LET语句对宏变量值的更新过程: 1)在第1~5行、第6~9行代码中分别定义了宏outer和宏inner。 2)当SAS宏处理器处理到第10行时,宏处理器仅搜索全局宏变量 表,表中未包含名为x的变量,宏处理器在表中创建宏变量x,赋值为 0,具体如表7.4所示。 3)在第11行,宏处理调用宏outer,也就是执行第1~5行。其中第4 行为调用宏inner,所以接下来的执行顺序为:第2~3行→第6~9行→第5 行。 4)第2行显式声明了一个局部宏变量x,宏处理器创建局部宏变量 表,称之为outer局部宏变量表,并在表中创建宏变量x。接着,在第3 行,将宏变量x赋值为1,宏处理器首先搜索outer局部宏变量表,该表中 含有x,则将其值赋为1。赋值结束后,outer局部宏变量表如表7.5所 示。 表7.4 表7.5 全局宏变量表 outer局部宏变量表 5)宏处理器执行第6~9行。 6)在第7行中,local语句声明了一个局部宏变量,宏处理器创建了 一个inner局部宏变量表,如表7.6所示。 7)执行第8行,解析x的值。宏处理器首先搜寻inner局部宏变量 表,接着搜寻outer局部宏变量表(这时还处在宏outer内部),其包含变 量x,将该x的值1赋给了y,如表7.7所示。 表7.6 表7.7 inner局部宏变量表 更新后的inner局部宏变量表 8)第9行,inner宏结束,删除inner局部宏变量。 9)执行第5行,outer宏结束,删除outer局部变量表。 宏语言与其他SAS语言 7.5 宏语言作为SAS语言的一部分,通常与SAS语言的其他部分一起使 用。本节将介绍宏语言与DATA步、宏语言与SQL语言之间的交互使 用。不过,在此之前,先要介绍SAS宏语言的编译过程。 7.5.1 宏语言的编译过程 一段SAS程序可以是以下一种或若干种的组合: ·DATA步和PROC步 ·全局语句 ·SQL语言 ·宏语言 当用户提交一段SAS程序后,SAS执行以下步骤: 1)在内存中创建输入栈(Input Stack)用来存储用户提交的代码。 2)SAS的组件之一文字扫描器(Word Scanner)对输入栈中的代码 进行从上至下、从左到右的扫描,在扫描过程中, a.若扫描到分号,则编译已扫描的内容,如果出错,在日志中显示 相关信息,否则继续扫描; b.如果扫描到符号“&”和“%”,调用宏处理器(Macro Processor)完 成对“&”和“%”的具体操作,返回继续扫描; c.如果扫描到边界(如RUN语句、另一个DATA步或PROC步),则 进行下一步。 3)执行已编译的内容,回到第1步,从Input Stack中读取剩余的代 码。 从上面的步骤可以看出SAS对宏语言的处理有着优先级:在第2步 的b阶段,SAS完成宏的处理后,返回编译,最后才执行不含宏语言的 其他编译无误的代码。然而,在处理实际问题的过程中往往需要在执行 阶段生成宏变量,具体见例7.19。 在2.1.5节中,我们提到注释的基本方法有两种:“*消息;”和“/*消 息*/”。在宏语言环境中,前者不起作用了,但后者仍然适用。此外, 宏语言中的注释还可以用“%*消息”的方法。推荐使用“/*消息*/”来注 释。例如,以下代码第3行的注释不起作用,第4行与第5行的注释起作 用,因此日志中输出a=4。 1. 2. 3. %macro comment; %let a=1; *%let a =4; 4. 5. 6. 7. 8. /* %let a=2; */ %*let a=3; %put a = &a; %mend; %comment 例7.19:数据集order_fact包含了订单时间(order_date)、订单类型 (order_type)、订单数量(quantity)以及价格(retail_price)。根据数 据集order_fact建立一个新的数据集orders,并根据输入的年份与月份, 统计订单类型为3的总数,如果该时间段内订单类型为3的总数为0,那 么输出数据集orders的标题为“No Type 3Order”,否则,输出标题“Some Type 3Order”。示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. 21. 22. 23. 24. 25. 26. 27. 28. 29. 30. 31. 32. 33. 34. data order_fact; informat order_date ddmmyy10.; input order_date order_type quantity retail_price; datalines; 05/01/2013 1 1 117.60 07/02/2013 2 2 656.6 07/02/2013 1 2 129.0 09/02/2013 1 2 36.2 16/02/2013 2 1 29.4 27/02/2013 1 5 192.0 ; run; %let month = 2; %let year = 2013; data orders; keep order_date order_type quantity retail_price; set order_fact end=final; where year(order_date)=&year and month(order_date)=&month; if order_type=3 then Number+1; if final then do; put Number=; if Number=0 thendo; %let foot= No Type 3 Order; end; else do; %let foot= Some Type 3 Order; end; end; run; proc print data=orders; format order_date mmddyy10.; title "Order in &year-&month"; footnote "&foot"; 35. run; 提交上述代码,相应输出结果如图7.23所示。 图7.23 新建数据集orders及其标题 可以看出,虽然没有看到该时间段内有类型3的订单出现,但是输 出标题仍然为“Some Type 3Order”。该错误的原因在于SAS对宏语言的 处理有着最高的优先级:当用户提交该程序时,SAS对其进行编译,在 第24行,SAS调用了宏处理器创建了宏变量foot,并赋值为“No Type 3Order”;接着,在第27行,SAS遇到该赋值语句时,由于foot已经存在 于宏变量表中,SAS修改了foot的值为“Some Type 3Order”。这时候,该 DATA步内所有的宏语句已经处理完毕,编译完data orders部分的代码如 下: data orders; keep order_date order_type quantity retail_price; set order_fact end=final; where year(order_date)=&year and month(order_date)=&month; if order_type=3 then Number+1; if final then do; put Number=; if Number=0 then do; end; else do; end; end; run; 当SAS遇到run语句时,开始执行该代码,然而无论number的值为 多少,都不会修改宏变量foot的值,因而,在proc print里面的调 用“&foot”的结果总是“Some Type 3Order”。如果SAS能在run之后,也就 是执行阶段生成宏变量foot,那么就可以产生正确的结果。在下一节 中,将会具体讲述如何在执行阶段产生宏变量。总之,相比其他语言, 宏语言处理的优先级更高。 7.5.2 宏语言与DATA步 1.SYMPUT函数 宏语言与DATA步语言之间的“沟通”是主要是通过变量的传递来实 现的。具体说,SAS允许用户在DATA步执行过程中生成一个宏变量, 也允许用户在DATA步执行过程中调用宏变量。二者之间变量的生成与 使用主要是通过函数SYMPUT与SYMGET来实现的。 函数SYMPUT用来在DATA步中动态生成变量,其语法如下: CALL SYMPUT(宏变量, 文本); 其中,宏变量指定了一个宏变量的名称,该函数的作用是将参数文 本包含的字符值赋予以这个名称命名的宏变量。 宏变量与文本可以是以下这些: ·常量(必须用引号括起来) ·DATA步变量 ·DATA步中的字符表达式 例7.20:修改例7.19中的%LET语句,使得PROC PRINT在调用foot 时可以正确地显示结果。 示例代码如下: %let month = 2; %let year = 2013; data orders; keep order_date order_type quantity retail_price; set order_fact end=final; where year(order_date)=&year and month(order_date)=&month; if order_type=3 then Number+1; if final then do; put Number=; if Number=0 then do; call symput('foot',' No Type 3 Order '); end; else do; call symput('foot',' Some Type 3 Order '); end; end; run; proc print data=orders; title "Order in &year-&month"; footnote "&foot"; run; 用户提交上述程序后,SAS开始对程序进行编译,CALL SYMPUT 语句在编译阶段未被执行,当编译到RUN语句时,SAS开始执行该段程 序。在IF FINAL语句之前,Number的值为0,于是SAS执行以下语句: call symput('foot','No Type 3 Order); 结果是宏处理器创建了foot宏变量,并赋值为“No Type 3Order”,输 出结果如图7.24所示。 图7.24 使用CALL SYMPUT语句后数据集orders的脚注 需要注意的是,%LET语句赋值是不需要加引号的;而使用CALL SYMPUT时,若变量名与赋值内容是常量(不是由其他表达式生成), 则都必须加引号。此外,从SAS 9.0开始,SAS新增了一个宏函数CALL SYMPUTX,其语法、含义与CALL SYMPUT基本一致。二者的区别如 下: ·在文本赋值给宏变量的过程中,使用CALL SYMPUTX会删去文本 的头尾空格,而CALL SYMPUT不会。 ·当文本是数值型时,在赋值过程中,数值将被转化成字符,CALL SYMPUTX用于存储字符的宽度可以长达32,且日志中不会输出类型转 化信息;而CALL SYMPUT用于存储的宽度仅为12个字符,且日志输出 类型转化信息。 2.SYMGET函数 SYMGET函数与SYMPUT函数的使用规则类似,二者都是在执行阶 段处理变量的,SYMPUT是在DATA步中生成宏变量;而SYMGET是在 宏变量表中获取变量及其值到PDV里面。函数SYMGET的使用语法为: SYMGET(参数); 其中,参数可以是以下这些: ·一个宏变量名,必须加上引号。 ·DATA步中的字符型变量名,该变量名的值是一个宏变量名,使用 时不加引号。 ·字符表达式,其结果是宏变量名。 例7.21:表student中包含了学生的名字和level,其中level的可能取 值为:L1、L2和L3。下述代码定义了3个宏变量,分别为L1、L2和L3, 并分别对它们进行了赋值。在SAS执行生产表student_level的过程中,读 入表student,根据student中level的值,把它们替换为easy、moderate或 者hard。 示例代码如下: %let L1 = easy; %let L2 = moderate; %let L3 = hard; data work.student; input name $ level $; datalines; Steve L1 Jim L2 Abby L1 Scott L3 Peter L2 ; run; data work.student_level; set work.student; intensity = symget(level); run; proc print data = work.student_level noobs; title "Using SYMGET Function in DATA Step"; run; 输出结果如图7.25所示。 图7.25 SYMGET函数的使用 使用SYMGET和“&”都能从全局或局部变量表中获取相应的宏变量 的值,不同的是SAS对“&”的处理是在编译阶段,而SYMGET是在执行 阶段(如例7.21所示)。 7.5.3 宏语言与SQL语言 正如前一章所述的,PROCSQL是SAS编程的一个重要部分,SAS同 样支持在PROCSQL中生成宏变量。本节将着重阐述如何在PROC SQL 语言中生成宏变量。 INTO语句 PROC SQL语言通过INTO语句来支持宏变量的定义,其语法如下: PROC SQL; SELECT 变量1, 变量2 INTO:宏变量名1, :宏变量名2 FROM 表格名 WHERE 语句 ORDER BY 变量1, 变量2 ; QUIT; 其中: ·不同宏变量名之间用逗号隔开,每个宏变量名前均要加上冒号。 ·若变量头尾带空格,那么生成的相应宏变量中头尾也带空格。 例7.22:在PROCSQL中生成宏变量total,使该变量的值为表 sashelp.orsales中1991Q1季度profit总额。 示例代码如下: 1. 2. 3. 4. 5. 6. proc sql noprint; select sum(profit) into: total from sashelp.orsales where quarter = '1999Q1'; quit; %put the total profit of 1991Q1 is: &total; 在上述代码第2行中,SELECT语句把sum(profit)的值赋给了宏变 量total;在第6行,%PUT语句在日志中输出该宏变量的值,结果如图 7.26所示。 图7.26 注意 使用INTO生成宏变量(一) 上述宏变量total的解析值中包含空格,默认情况下,INTO 中宏变量的存储格式为Best12.,当变量值的长度小于12时,SAS在其前 面补上空格。若用户不希望有空格,则可以在引用该变量前使用%LET 语句删去空格,例如“%LET total=&total;”。 上述INTO语句也可以用来把不同的变量值赋值给同一个宏变量, 用户可以在宏变量名后加上SEPARATED选项来对不同变量值进行分 隔,其使用语法如下: SEPARATED BY '分隔符' 其中,分隔符可以是任何文本与符号,常见的有空格、逗号等。 例7.23:在代码中生成一个宏变量,其值包含数据集sashelp.orsales 中变量product_line的所有可能取值,不同取值间用逗号隔开。 示例代码如下: proc sql noprint; select distinct product_line into: all_product_lines separated by ', ' from sashelp.orsales; quit; %put all distinct product lines are: &all_product_lines; 日志中显示的结果如图7.27所示。 图7.27 使用INTO生成宏变量(二) 宏编程 7.6 在本章开始提到了使用宏语言可以实现条件控制。宏语言的控制功 能大体可分为:条件语句与循环语句。 7.6.1 条件语句 %IF%THEN语句用来实现对宏语言的条件控制功能,其语法如 下: %IF 表达式 %THEN 文本1 %ELSE 文本2 其中,文本1和文本2可以是以下这些: ·宏程序语句 ·一个表达式 ·宏的解析 ·引用的宏变量 ·固定的字符串 语句的含义是:当表达式为真时执行文本1,否则执行文本2。 表达式中若涉及字符比较,右边的字符常量不需要加引号,并且区 分大小写。因而,实际应用中常常结合%UPCASE来使用,具体见下 例。 例7.24:条件语句中的逻辑比较。 示例代码如下: %let place = Us; %macro empty; %if &place = US %then %put Not case sensitive; %else %put macro comparison is case sensitive; %mend; %empty 日志中显示的信息如图7.28所示。 图7.28 条件语句中的逻辑比较 当特定的条件分支下需要执行一组SAS代码时,则需要用“%DO%END”来界定,具体如下: %IF 表达式 %THEN %DO 语句组1 %END %ELSE %DO 语句组2 %END; 上述语句组可以包含一个或者多个SAS语句。 下面用一个例子来介绍如何实现条件控制。 例7.25:数据集Daily_order(如图7.29所示)包含了每天的销售记 录。管理人员需要生成每天销售记录的报表,如果当天是周五,则需要 生成周报表。 图7.29 数据集Daily_order 下面代码实现定义了宏reports,该宏根据当前系统日期,自动判断 应生成报表的种类,即是当天报表还是周报表。 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. 14. 15. 16. 17. 18. 19. 20. %macro reports; %if &sysday=Friday %then %do; proc means data=daily_order n sum mean; where order_date between "&sysdate9"d-6 and "&sysdate9"d; var quantity total_price; title "Weekly sales: &sysdate9"; run; %end; %else %do; proc sql; select * from daily_order where order_date="&sysdate9"d; title "Daily sales: &sysdate9"; quit; %end; %mend reports; %reports 上述代码的第2行中,系统宏变量&SYSDAY返回的是当前的星 期,若该值为Friday,那么将执行第3~9行代码;否则执行第11~18行代 码。第3~9行代码将输出周报表;第11~18行代码生成当天的报表。 表7.8总结了宏条件语句与DATA步中条件语句的差别。 表7.8 宏语言与DATA步中条件语句的对比 7.6.2 循环语句 除了上节介绍的条件语句外,另一常见的控制语句是循环语句,其 作用是在一定条件下重复执行某些宏语句或者重复产生某些SAS代码。 设定循环的%DO语句的语法如下: %DO 指标变量=开始值 %TO 结束值<%BY 增量> 文本或者宏语句 %END; 其中: ·指标变量是宏变量名或者能产生宏变量名的表达式。当该宏变量 名在宏变量表中不存在时,宏处理器自动在局部宏变量表中创建该宏变 量。 ·开始值、结束值与增量可以是能产生整数的宏表达式或者宏语 句,如解析宏变量。 ·默认情况下,%BY语句中的增量为1。 根据上述第一点,在局部变量表创建指标变量前,SAS会搜索所有 的宏变量表,因此,若多个局部宏变量表含有相同的变量名,有可能出 现问题,具体见下例。 例7.26:%DO循环语句中的指标变量。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. %macro func1; %do i=1 %to 3; %end; %put inside func1; %mend; %macro func2; %do i=1 %to 3; %func1 %put func2; %end; %mend; %func2 在上述代码的第12行中,调用了func2,SAS开始执行第7~10行语 句。在第7行,指标变量i不存在于任何宏变量表中,于是SAS在func2局 部宏变量表中创建该变量,并赋值为1。接着执行第8行:调用func1, 执行第2~4行,在第2行又出现一个指标变量i,此时i在局部宏变量表 func2中已经存在,所以SAS更新该值,执行完第4行后,局部宏变量表 func2中的i值为4。第9行,在日志中输出func2,这时i的值为4,func2中 的循环结束。整个程序结束,日志中输出结果如图7.30所示。 图7.30 调用func2后日志输出结果 若用户希望连续调用%func1三次,可将func1中的i修改为局部宏变 量,即在第2行的定义中,加上“%local i;”,使其有效范围仅局限于宏 func1中。因此,建议在使用指标变量时应尽量将其定义成局部宏变 量。 例7.27:在代码中对数据集sashelp.class中变量name的每一个值都生 成一个宏变量,并且在日志中输出这些宏变量的值。 示例代码如下: 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11. 12. 13. data_null_; set sashelp.class end=no_more; call symput('name'||left(_N_),(trim(name))); if no_more then call symput('count',_N_); run; %macro putloop; %local i; %do i=1 %to &count; %put name&i is &&name&i; %end; %mend; %putloop; 上述代码的第2行中,end=no_more语句对no_more赋值,DATA步 读取到最后一个观测值时no_more的值为1(也就是真),否则为0;第3 行中“_N_”为当前的观测值数,LEFT函数移除了“_N_”前的空格,随着 观测值数的变化,CALL SYMPUT语句会生成一系列的宏变量,并把 name的赋值给这些宏变量;第4行,当DATA步读取到最后一行时,把 行数赋值给宏变量count。 第7~12行定义了putloop宏。其中,第8行声明了一个局部宏变量i; 第9~11行用一个迭代语句在日志中输出这些宏变量。最终日志中的输出 如图7.31所示。 图7.31 使用循环语句输出宏变量的值 宏语言中控制迭代的命令除了上述的%DO-%TO方式以外,还 有%DO-%WHILE和%DO-%UNTIL两种方式。其中,%DO-%WHILE语 句的使用语法如下: %DO %WHILE (表达式); 文本或者宏语句 %END; 类似的,%DO-%UNTIL的使用语法如下: %DO %UNTIL(表达式) 文本或者宏语句 %END; %DO-%WHILE语句先判断表达式,后执行文本;而%DO-%UNTIL 语句先执行文本,后判断表达式。因此,%DO-%UNTIL语句至少执行 一次。 例7.28:在代码中定义带参数的宏print_multiplies,用来一次打印多 个文件。假设数据管理人员有多个文件要同时打印,只需要调用宏 print_multiplies,在参数位置填写所有要打印表格的名称,且用空格隔 开即可。 示例代码如下: 1. %macro print_multiplies(dsns); 2. %let i = 1; 3. %let current_data = %scan(&dsns, &i,' '); 4. %do %while (&current_data ne ); 5. proc print data = &current_data; 6. run; 7. %let i=%eval(&i+1); 8. %let current_data = %scan(&dsns, &i, ' '); 9. %end; 10. %mend; 11. %print_multiplies(sashelp.class sashelp.classfit) 执行上述代码,第1~10行,定义了一个带参数的宏 print_multiplies,经SAS宏处理器编译无误后,存储起来。第11行,调 用该宏,SAS开始执行第1~10行。 实现SAS在局部宏变量表内创建一个名为dsns的局部宏变量,并赋 值为sashelp.classsashelp.classfit。在第3行中,%SCAN函数返回变量dsns 中的第一个词,即sashelp.class,并将该值赋给current_data。第4行,判 读current_data是否为空,这里为非空,因此执行第5~8行,其中第5~6行 打印当前数据集,即current_data中的值,第7行更新i的值,第8行利 用%SCAN函数取出dsns中的第二词,即为下一个待打印的数据集名, 将该名赋值给current_data。重新回到第4行,重复上述过程,直至 current_data值为空。 最后,列举一些使用宏语言编程的技巧来结束本章内容: ·可以在事先在DATA步、PROC步以及SQL中测试程序,确认无误 后,再将其用宏语言“包装”起来。 ·尽量避免写较长的代码。可以编写若干个较小的宏(如一个宏实 现一个特定的功能),进行调试,然后再将这些较小的宏组合起来。其 中,可以充分利用宏语言的系统选项来调试程序。 ·尽量避免使用全局宏变量。 ·选择不易重复的字符作为全局宏变量名,例如用下划线开头等。 7.7 本章小结 本章主要介绍了SAS宏语言的有关知识。首先,讲解了对宏变量进 行定义、调用、查看和删除的方法;其次,介绍了SAS函数在宏语言中 的调用方法和常见的宏函数;接下来,介绍了如何在SAS中使用宏语言 定义宏、调用宏和存储宏,并介绍了宏的编译原理,以及宏语言与 DATA步和SQL过程的交互;最后,介绍了宏语言中条件语句和循环语 句的使用方法。 第8章 开发多语言支持的SAS程序 SAS软件支持国际化,并且已经针对全球大部分地区推出了本地化 版本。在不同语言的操作环境下,SAS软件的界面、输出、日志等均使 用当地语言,并符合当地的标准和规范。SAS也提供国际语言支持 NLS(National Language Support)功能,可利用该功能开发NLS应用, 从而对本地区语言数据进行分析处理,之后则以符合本地区语言规范的 形式展示分析结果和信息。基于NLS特性,还可以开发多语言支持 MLS(Multi-lingual Support)的SAS应用,同一SAS程序在不同的语言/ 区域设置下,对数据的分析处理以及分析的结果和信息展示都会符合相 应国家和地区的语言、文化习惯。 本章介绍了开发多语言支持的SAS应用程序的相关概念和特性,包 括国际化、本地化、语言/区域、编码字符集、SAS NLS支持、NL格式 和输入格式、字符串外部化等。其中的很多概念和使用其他编程语言开 发时并没有什么不同。在理解这些概念和SAS的NLS特性的基础上,读 者不仅可以处理与当前SAS会话编码不同的数据,而且可以开发多语言 支持的SAS应用。 多语言支持的基本概念 8.1 多语言支持的应用程序指该程序在世界各地使用时,其能够处理的 数据,以及处理数据的方式、信息展现的方式都符合当地的语言、文化 习惯,这要求应用程序在运行时,能够自动进行与地区、语言相关的处 理,也就是通常所说的国际化。国际化(Internalization)是指在设计软 件时,充分考虑并满足软件将运行于不同地区和语言环境下这一要求的 过程,也就是说,在设计软件时不假定该软件是基于某个语言和地区 的,当软件被应用到不同的语言及地区时,软件代码无需改变或修正。 本地化(Localization)则是在国际化的基础上,加上与特定语言/ 区域(Locale)有关的信息和翻译文件的过程,例如翻译用户界面、系 统消息和文档等。国际化是本地化的基础,产品实现了国际化,表示产 品可以通过本地化使其适用于任何语言和地区,而本地化则是为了在特 定的地区使用而做的额外工作。例如,在中国地区产品要实现本地化, 其界面就需要是中文的,从原来的英语转译为中文的过程则属于本地化 的范围。全球化(Globalization)经常用来表示国际化和本地化的组 合。 8.1.1 语言/区域 语言/区域(Locale)反映某一地理区域的语言、本地习俗(例如数 据格式),以及文化。本地习俗包括国家或地区的数字、日期时间和货 币符号的特定格式。除此以外,语言/区域还包括字符排列规则、纸张 大小设置、邮政地址、电话号码等。对语言/区域进行设置主要是指对 给定语言/区域下的输出或信息展现进行格式化。软件通常会根据Locale 值来确定其与语言文化习俗相关的行为。 例如美国、中国、法国的短格式日期、数字、货币符号之表示法如 表8.1所示。在短日期格式中,这3个国家日期中的年、月、日的顺序, 以及货币符号都不尽相同。在数字表示上,千分位符号、小数点符号, 美国与中国一致,而法国则不同。在表示货币金额时,美元符号($) 在数字之前,人民币(¥)和欧元符号 在数字之后。因此,支持这3 种语言的应用程序至少能够正确处理和输出对应形式的日期、数字和货 币表示。 表8.1 美国、中国、法国的日期、数字、货币格式 语言属于Locale,但是语言并不唯一对应任何Locale。例如,使用 中文的地区有中国大陆、中国香港、中国台湾、中国澳门等,但这些地 区的文化习俗不尽相同。同时,一个国家也可能有多种官方语言。例 如,加拿大有两种官方语言,英语和法语。通常会使用区域标识符 LCID(Locale ID)对世界各地区及语言进行统一标识。LCID至少由语 言标识符和区域标识符组成。POSIX标准的区域标识符的形式为“语言_ 区域”,例如上述使用中文的地区,它们的区域标识符分别为zh_CN、 zh_HK、zh_TW、zh_MO和zh_SG。同样,加拿大使用英语和法语的区 域标识符分别为en_CA和fr_CA。 8.1.2 字符集和编码 字符集(Character Set)是由一种或一组语言使用的字符和符号组 成的集合。字符集包含本地字符、特殊字符(标点符号)、不带音标的 大小写拉丁字符(a~z和A~Z)、数字0~9和计算机需要使用的控制字 符。字符集编码(Character Set Encoding)是指将一个字符集中的每个 字符映射为一个唯一的数字,所有的字符及对应的数字构成了码点 (Code Point)表,所对应的数字则称为该字符的码点。码点表也称为 码页(Code Page),通常按照字符的码点进行排序。数据在计算机中 以其包含的字符编码进行存储。 例如,图8.1给出了以GBK编码的码页中C3、C4区所表示的字符。 C3区的汉字“米”在该分区的第D行,第7列,那么汉字“米”对应码点的十 六进制表示为“C3D7”。同样,C4区的汉字“牧”在GBK编码中码点的十 六进制表示为“C4C1”。 图8.1 GBK编码的C3和C4区字符 1.常用的编码方法 编码方法通常是由各个计算机硬件制造商和标准组织开发的标准。 常用的编码如下: (1)美国信息交换标准编码ASCII(American Standard Code for Information Interchange) 一种7位编码,提供128个字符,常用于个人计算机。 (2)广义二进制编码的十进制交换码EBCDIC(Extended Binary Coded Decimal Interchange Code)系列 一种8位编码,提供256个字符,常用于IBM Mainframe和大多数 IBM中型计算机。 (3)国际标准化组织ISO(International Organization for Standardization)646系列 一种国际标准的7位编码,提供128个字符。ISO 646系列与ASCII相 似,但是它使用几个标点符号兼做附加符号,用于表示特定语言需要的 变体字符(例如带尖音、重音、分音等的字符),并且开放了其中12个 符号的编码(称为国别用途码位),允许各国用于表示不同的字符。该 系列编码已被更现代的ISO 8859所取代。 (4)ISO 8859系列 在ASCII编码之上的8位编码,共提供256个字符。ISO 8859系列支 持所有ASCII码点,并直接编码表示变体字符。常见的Latin1-10、 Cyrillic、Arabic、Greek、Hebrew、Thai都属于ISO 8859系列。Latin1, 官方名称为ISO-8859-1,是该系列编码中最常用的成员,包含ASCII字 符、重音字符等西欧语言需要的其他字符和一些特殊字符。 (5)GB 2312、EUC-CN、GBK 国标码是“中华人民共和国国家标准信息交换用汉字编码”的简称。 常用国标字符集为GB 2312或GB 2312-80,共收录6763个汉字,以及包 括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在 内的682个字符。EUC-CN是GB 2312的扩展UNIX编码EUC(Extended UNIX Code)的表示形式。 GBK,全称为汉字内码扩展规范(Chinese Internatial Code Specification),是GB 2312字符集的扩展字符编码。GBK完全兼容 GB2312-80编码,支持GB 2312-80的不支持的部分中文姓氏、繁体中 文、日文假名,还包括希腊字母及俄语字母等字符。微软Windows操作 系统简体中文版采用该编码。 (6)BIG5码、MS-950 BIG5,又称大五码,是针对繁体汉字的汉字编码,目前在中国台 湾、中国香港地区的计算机系统中得到普遍应用。MS-950是微软的繁 体中文字符集标准,由标准BIG5码修改而成,是BIG5最通行的版本, 也是BIG5码的事实标准。 (7)Unicode 基本包含世界上所有的文字。有3种编码形式:UTF-8、UTF-16和 UTF-32,较常用的为UTF-8编码。UTF-8是一种以8位(bit)为单位的 变长度编码方式,可以表示Unicode字符集中的所有字符。每个以UTF-8 编码存储的字符,当第一个字节的第一位是0时,则表示该字符占一个 字节。如果第一个字节不以0开始,则第一个字节会包含该字符所占字 节数的信息,且其余字节的头两位都是以“10”开始的。在UTF-8编码 中,ASCII字母使用1个字节存储,重音文字、希腊字母和西里尔字母使 用2个字节存储,常用的汉字使用3个字节,辅助平面字符则使用4个字 节。 2.转码 字符在不同的码页中可能占有不同的位置,如果操作环境所使用的 语言/区域设置和编码不同,那么它们在交换数据时可能就需要进行转 码。转码(Transcoding)是将数据从一种编码形式转换成另一种编码形 式。例如,在Windows Latin1码页中,德语中的字符的十六进制表示为 C4,而在德语EBCDIC码页中,其码点为4A。所以,如果将在Windows Latin1编码的UNIX操作环境上创建的文件传输到IBM Mainframe操作环 境上,文件中的数据就需要从Windows Latin1编码重新映射到德语 EBCDIC编码。如果数据中包含字母?,则该字母的码点会从C4转换成 4A。可以看出,转码不是在语言之间进行翻译,而是将字符重新映射 到不同编码中的码点。 转码中经常出现的问题是乱码。当原字符集中的字符在目标字符集 中不存在时,该字符会显示为乱码。例如从简体中文编码euc-cn转到 Latin1时,由于中文汉字在Latin1中不存在,因此中文汉字会显示为乱 码。此外,转码后的字符所占的长度可能会超过原字符长度,这时,如 果存储该字符所在字符串的变量的长度不够,则会产生截断。例如在将 数据集从euc-cn编码转换到UTF-8编码时,因为中文汉字在euc-cn中的编 码长度为2个字节,而有些汉字在UTF-8编码中的长度为3个字节,此时 就会存在该字符变量的长度无法容纳其所有字符的UTF-8编码的情况, 这样一来,转码后产生的字符串实际上是被截断后的字符串。 3.SBCS、DBCS、MBCS 根据字符集编码中表示字符所需要的字节数,字符集经常分为单字 节字符集、双字节字符集和多字节字符集。 单字节字符集SBCS(Single-Byte Character Set)指在该字符集中的 每个字符都以一个字节表示。一个字节共8位,仅能支持28=256个码 点,因此限制了在该字符集中的可用字符数。256个码点的字符集足够 支持大部分欧洲语言,例如英语、法语、德语等。但是对一些具有更多 字符的语言,例如中文和日语则完全不够,所以需要使用更多字节来表 示这些语言支持的字符。 双字节字符集DBCS(Double-Byte Character Set)指该字符集中的 字符最多由2个字节表示。SAS使用的简体中文编码euc-cn、繁体中文编 码ms-950、日语编码shift-jis和韩语编码euc-kr均属于双字节字符集编 码,这4种编码通常简称为CCJK。 多字节字符集MBCS(Multiple-Byte Character Set)是指该字符集中 的字符以多个字节来表示。最常用MBCS是UTF-8字符集,在该字符集 中,一个字符最多需要4个字节表示。DBCS也是一种MBCS。 8.2 NLS相关的SAS选项 SAS的本地区语言支持NLS,使得SAS软件产品可以开发出符合本 地区语言规范的SAS应用程序,从而使全球各地区的SAS用户(例如亚 洲、欧洲的用户)都能够以本地区语言和环境成功地处理数据。这种支 持对在客户端/服务器环境下运行SAS应用的国际用户尤为重要。 SAS应用程序的NLS可用于本地化和国际化,结合了NLS功能的 SAS应用程序可避免软件功能对语言或文化特定规范(例如字符分类、 字符比较规则、字符集、日期和时间格式、界面、消息文本的语言、数 字和货币格式,以及排序顺序等)的依赖。同时SAS还提供了保证本地 特殊字符能够正确地显示或打印的功能。 接下来主要介绍SAS NLS特性的主要选项:LOCALE=、 ENCODING=,TIMEZONE=以及一组语言切换选项。SAS NLS特性中 关于NL格式或NL输入格式、字符串操作及文本字符串外部化的内容在 后面的小节中会陆续进行介绍。 8.2.1 语言/区域选项LOCALE= SAS提供了语言/区域选项LOCALE=来处理和展示与语言、本地习 俗及文化相关的信息,例如数据格式、物理地区文化、当地文化习俗国 家或地区的数字、日期时间和货币符号等。SAS有自己定义的Locale标 识符,也可使用POSIX标准的Locale标识符来标识不同的区域。 表8.2给出了常见语言区域的SAS Locale标识符、POSIX标准的 Locale标识符及其别名。这3种形式在SAS中都可使用。其他语言/区域 Locale值请参考SAS帮助文档。 表8.2 常用语言/区域的Locale值 系统选项LOCALE=的值可以在SAS启动时指定,也可以在SAS启动 后通过OPTIONS语句更改。系统选项LOCALE=通常会影响SAS程序中 所有的NL格式和输入格式,以及字符串外部化机制中的SASMSG函数 和SASMSGL函数的执行结果,此外还涉及一些系统选项等。在SAS启 动时指定还是启动后指定系统选项LOCALE=,所影响的系统选项不 同。 1.指定系统选项LOCALE= (1)SAS启动时指定系统选项LOCALE= 通常,在启动SAS时,相应的Locale会在启动时加载的SAS配置文 件中指定。也可以通过在启动SAS的命令中指定选项LOCALE来覆盖 SAS配置文件中的语言/区域设置。在SAS命令中指定启动SAS会话的 Locale为中国大陆(zh_CN)的示例如下: #/opt/sas/SASHome/SASFoundation/9.4/sas -locale zh_CN SAS启动时指定了选项LOCALE后,它除了会作用于SAS程序中所 有的NL格式和NL输入格式、SASMSG函数和SASMSGL函数外,还会 影响以下系统选项:DATESTYLE=、DFLANG=、ENCODING=、 LOCALEDATA=、MAPEBCDICTOASCII=、ODSLANGCHG、 PAPERSIZE=、RSASIOTRANSERROR、TIMEZONE=、TRANTAB=、 URLENCODING=。关于这些选项的详细信息,以及对应不同Locale的 默认取值,请参考SAS帮助文档。 (2)SAS启动后指定系统选项LOCALE= SAS系统启动后,也可以通过OPTIONS语句来改变系统选项 LOCALE=,即当前SAS会话的语言/区域设置。在SAS启动后更改系统 选项LOCALE=为zh_CN的示例如下: options locale=zh_CN; 当系统选项DATESTYLE=、DFLANG=和PAPERSIZE=的值为 LOCALE时,这些选项的取值会随着Locale的变化而变化。 2.查看SAS会话的语言相关设置 在SAS启动后,可使用第2章中介绍SAS系统选项时描述的方法来查 看当前SAS会话的Locale,例如OPTIONS过程、GETOPTIONS函数指定 选项名称或SAS系统选项窗口。这里不再给出示例。还可通过在 OPTIONS过程中指定选项组LANGUAGECONTROL,来将当前SAS会 话中跟语言相关的选项及选项值输出到日志中。例如,在Windows环境 下,启动简体中文版SAS,提交如下代码: proc options group=languagecontrol; run; 打印在日志窗口的消息如图8.2所示。 图8.2 8.2.2 语言控制组选项输出 编码选项ENCODING= 编码是SAS会话和要处理分析的数据的重要属性。为了保证数据在 SAS会话中能够正确地被分析处理,SAS提供了系统选项ENCODING= 以及一些数据集选项、逻辑库选项和读写文件选项,便于灵活地处理各 种编码的数据。 1.SAS会话编码 系统选项ENCODING=用于指定SAS会话的编码,即SAS会话使用 哪种编码处理数据。SAS会使用该编码构造处理SAS语法和数据集,以 及读写外部文件的环境。 SAS会话的编码只能在SAS启动时指定,而且在SAS启动后不可更 改。可通过选项LOCALE=指定SAS会话编码为指定区域/语言的默认编 码,或者通过选项ENCODING=来指定。确定选项ENCODING=值的顺 序如下: ·如果在SAS启动时指定了选项ENCODING=的值,则为该值。例 如,在SAS启动时使用选项ENCODING=指定会话编码为euc-cn的形式 如下: #/opt/sas/SASHome/SASFoundation/9.4/sas -encoding euc-cn ·如果未指定选项ENCODING=的值,但是指定了选项LOCALE=, 则选项ENCODING=的值为指定Locale在相应操作环境下的默认值,如 表8.3所示。注意,在不同的操作环境下,相同的语言/区域选项,SAS 所使用的字符集编码(即选项ENCODING=的值)可能不一样。 表8.3 常用Locale设置下的SAS会话编码 注:①跟平台相关。 ②跟平台相关。 ·如果在SAS启动时既没有指定LOCALE=也没有指定 ENCODING=,SAS会将编码设置为默认值。在Windows操作环境上, 选项ENCODING的默认值为wlatin1,在UNIX下默认值为latin1。 在SAS启动后,可使用前面介绍的查看选项LOCALE=的方法,来 查看选项ENCODING的值。 2.SAS数据集的编码 使用SAS 9创建的数据集文件在其描述信息中包含了编码信息。数 据集的编码默认为创建该数据集的SAS会话的编码,也可以通过选项覆 盖。 (1)查看数据集编码 可使用SAS资源管理器的数据集属性对话框、CONTENTS过程 (DATASETS过程的CONTENTS语句)或ATTRC函数来查看SAS数据 集的编码。 1)数据集属性对话框。在SAS资源管理器中找到该数据集,右键 单击该逻辑库,并在浮动菜单中选择“属性”,然后选择“详细信息”选项 卡,其中“编码”项给出了数据集的编码信息,数据集sashelp.class的编码 为euc-cnSimplified Chinese(EUC),如图8.3所示。 2)使用CONTENTS过程(或DATASETS过程的CONTENTS语 句)。该过程运行结果的第一个表格中会给出指定数据集的编码信息。 使用该方法打印的包含数据集sashelp.class的编码信息表格如图8.4所 示,其编码为euc-cnSimplified Chinese(EUC)。 图8.3 数据集“属性”对话框 图8.4 CONTENTS过程输出 3)使用ATTRC函数。使用ATTRC函数可获取数据集sashelp.class文 件的属性ENCODING,并打印在日志窗口。 %let dsid=%sysfunc(open(sashelp.class,i)); %put %sysfunc(attrc(&dsid,encoding)); 日志窗口中显示其编码为euc-cnSimplified Chinese(EUC),如图 8.5所示。 图8.5 ATTRC函数返回数据集编码 (2)读取和写入SAS数据集 默认情况下,SAS使用当前会话的编码读写SAS数据集。但是,必 要时可通过选项来覆盖或更改当前数据集的编码。影响SAS数据集编码 的选项如表8.4所示。 表8.4 影响SAS数据集编码的选项 注:数据表示(Data Representation)指在特定操作环境下数据的存 储格式,包括浮点数存储、字符编码、内存中字节顺序(大端还是小 端)、单词对齐(4字节边界还是8字节边界)、整型数据类型长度等。 当使用了数据集选项OUTREP=时,SAS会使用由OUTREP=的值指定的 操作环境默认的会话编码,将数据写入新数据集文件。 当在SAS 9中创建数据集时,如果所创建的数据集为新数据集, SAS使用当前会话的编码将数据写入数据集。但是,如果新数据集使用 了表8.4中的选项,则数据集文件的编码由这些选项确定。其优先顺序 依次为数据集选项ENCODING=、数据集选项OUTREP、LIBNAME选 项INENCODING=和OUTENCODING=(这两个选项常用于使用包含混 合编码的数据集的逻辑库)。 如果要写入的数据集文件存在,则修改或更新的文件会继承原来文 件的编码。但如果存在的数据集由另一种操作环境所创建(例如当前 SAS会话操作环境为Windows,而数据集由UNIX操作环境下的SAS所创 建),或存在的文件不带编码信息(SAS 9之前的版本所创建的数据集 不包含编码信息),则使用当前的会话编码。 在读入数据集时,因为数据集文件中包含了编码信息,所以即使数 据集选项ENCODING=可以在读取输入数据集时使用,大多数用户也不 会去用,他们一般会使用其默认处理方式:如果数据集文件编码与当前 SAS会话的编码不兼容,数据集文件中的数据会被转码成会话编码,数 据集文件的编码不变;如果数据集文件没有编码,仅当文件的数据表示 与当前会话不同时才对数据进行转码。 3.读写外部文件 默认情况下,SAS使用当前会话的编码读取外部文件。SAS在读取 外部文件时,通常会假定外部文件的编码与当前SAS会话编码一样,并 使用当前会话编码读取外部文件的内容。例如,当通过读取外部文件创 建新数据集时,SAS会假定该外部文件的编码与会话编码一样。如果编 码不同,外部数据可能会被不正确地写入数据集中。 SAS提供了可用于覆盖默认编码的选项,以便指定与当前会话不同 的编码来读取外部文件。常用的选项为ENCODING=,用于%INCLUDE 语句、FILE语句、FILENAME语句、INFILE语句中指定输入或/和输出 文件的编码。SAS还提供了其他用于转码或覆盖默认编码行为,例如 ODSCHARSET=、XMLENCODING=等,有兴趣的读者可参考SAS帮助 文档进行学习。 例8.1:将数据集sashelp.cars中的数据输出为编码为UTF-8的外部文 件。 先在FILENAME语句中通过指定选项ENCODING=来实现该功能, 代码如下: filename outfile 'c:\sas\data\ch8\cars.dat' encoding="utf-8"; data _null_; set sashelp.cars; file outfile; put Make Model Type Origin DriveTrain MSRP EngineSize; run; 然后在DATA步的FILE语句中使用选项ENCODING=来实现该功 能,代码如下: filename outfile 'c:\sas\data\ch8\cars.dat'; data _null_; set sashelp.cars; file outfile encoding="utf-8"; put Make Model Type Origin DriveTrain MSRP EngineSize; run; 4.排序序列 排序序列(Collating Sequence)是字符排序的标准。例如,当 SORT过程执行时,排序序列决定一个字符相对于其他字符的排序顺序 (高、低或相等)。默认的排序是二进制排列,根据每个字符在SAS会 话编码的码页中的位置,也就是按照字符在码页中的码点来对字符进行 排序。 例8.2:对中国的各个城市按城市名称进行排序。 示例代码如下: data work.city; input City $; datalines; 上海 北京 广州 深圳 ; run; proc sort data=work.city out=work.city_st; by City; run; title "Locale=%sysfunc(getoption(locale))"; proc print data=work.city_st noobs; run; 在Locale为zh_CN和zh_TW的SAS会话中,排序结果由PRINT过程 打印如图8.6和图8.7所示。 图8.6 排序结果(Locale=zh_CN) 图8.7 排序结果(Locale=zh_TW) 在中国大陆,通常人们习惯于根据拼音的全拼字母在ASCII字符中 的顺序来进行排序,例如在字典、电话本中,而中国台湾则使用笔画进 行排序。为了遵循各地的语言习惯,SAS的SORT过程的语言排序规则 与之类似。 二进制排序是最快的排序类型,因为其对计算机来说最有效。但 是,如果不熟悉这种方法,在二进制排列的报告中很难进行查找。例 如,二进制排列的报告中将以大写字母开始的单词与以小写字母开始的 单词分开,将带有重音字符的单词排在不带重音字符的单词之后。这样 一来,对于ASCII编码来说,大写字母Z就会排在小写字母a前面。 在SORT过程中还提供了排序序列选项来指定其他排序序列,例如 指定转换表、编码值以及语言排序等。使用选项SORTREQ=指定其他排 序序列的基本形式如下: PROC SORT DATA=数据集 SORTSEQ=转换表|编码|LIGNUISTIC; RUN; 其中: ·转换表是SAS的目录条目,用于将数据从某一种单字节编码转换成 另一种单字节编码。转换表在排序时会重排字符。使用转换表进行排序 的数据集包含一个排序指示符,它可在CONTENTS过程的输出中将指 定转换表显示为排序序列。 ·编码为SAS系统选项ENCODING=支持的所有编码,包括多字节编 码。字符会从SAS会话编码转码到指定的编码,然后使用二进制排序。 使用编码值进行排序的数据集包含一个排序指示符,它可在 CONTENTS过程的输出中将指定编码值显示为排序序列。 ·LIGNUISTIC指定语言排列规则。根据指定语言规则对字符进行排 序,该结果与字典、电话本和书本索引中使用的排序类似。SAS合并了 UNICODE国际组件ICU(International Components for Unicode),并提 供了语言排序例程来实现语言排序,该语言排序兼容标准化的Unicode 排序算法UCA(Unicode Collation Algorithm)。在指定了选项 LINGUSTIC后,还可以为其指定其他选项来提供更加丰富的排序功 能,例如对中文中的音标或欧洲语言中的重音符号进行区分。 关于该选项的使用,请参考SAS帮助文档进行学习。 8.2.3 时区选项TIMEZONE= 在SAS 9.4中,SAS支持了新系统选项TIMEZONE=,该选项允许用 户指定时区ID或时区名称。若其默认值为空,表示SAS的时区与客户端 的时区相同。时区ID指的是SAS定义的地区/区域值格式,例 如'Asia/Beijing'。时区名称为指定3个或4个字符的时区名称,例 如'CST'。指定时区ID和时区名称相应的SAS代码如下: options timezone='Asia/Beijing'; options timezone='CST'; 设置的时区值会影响如下SAS组件。 ·事件或日志记录的时间。 ·数据集创建或修改的时间。 ·日期和日期事件函数:DATE、DATATIME、TIME、TODAY。 ·时区函数:TZONEOFF、TZONEID、TZONENAME、 TZONES2U、TZONEU2S。 ·时区格式:B8601DXw.、B8601LXw.、B8601TXw.、 E8601DXw.、E8601LXw.、E8601TXw.、NLDATMZw.、 NLDATMTZw.、NLDATMWZw.。 8.2.4 语言切换选项 从SAS 9.3开始,引入了语言切换(Language Switching)选项,并 且该选项在SAS 9.4中得到了增强。通过切换语言可以在同一个SAS会话 中产生多种语言形式的报表。在SAS Unicode Server上,这种功能尤其 有用。SAS Unicode Server是编码为UTF-8的SAS会话,因为该编码基本 上包含了世界上大部分的文字,所以在Unicode Server中使用语言切换 选项,能够保证各种语言的字符可正确显示。 SAS的语言切换特性由以下几组选项确定,这些选项在SAS启动时 有效。 (1)LOGLANGCHG|NOLOGLANGCHG 用于设置SAS日志消息的语言是否可以在SAS启动后被更改。 LOGLANGCHG为可以,NOLOGLANGCHG为不可以,默认为 NOLOGLANGCHG。如果指定了LOGLANGCHG,则日志语言由选项 LSWLANG=确定。 (2)ODSLANGCHG|NOODSLANGCHG 用于设置ODS输出中消息文本的语言是否可以在SAS启动后被更 改。ODSLANGCHG为可以,NOODSLANGCHG为不可以,默认为 NOODSLANGCHG。如果指定了ODSLANGCHG,则ODS输出的语言 由选项LSWLANG=确定。 (3)LSWLANG 用于在当LOGLANGCHG和/或ODSLANGCHG指定时,指定日志消 息和ODS输出中SAS消息文本的语言,所选择的语言必须与会话编码兼 容。该选项的默认值为LOCALE,表示为当前会话Locale的语言。该选 项不会影响NL格式的行为。 (4)LOGLANGENG|NOLOGLANGENG 用于将SAS日志消息的语言指定为英语。而且,如果SAS启动时指 定了ODSLANGCHG,则在SAS ODS输出中SAS消息文本的语言也为英 语,如果其为NOODSLANGCHG(默认值),则ODS输出中SAS消息文 本的语言为SAS会话启动时的语言。 通过这组选项,可实现如下场景。 场景1:ODS输出中SAS消息文本的语言符合SAS启动时选项 LOCALE=的设置,而日志消息的语言始终为英语。 想要实现上述场景,启动SAS会话时指定LOGLANGENG。注意, 在这种情况下,SAS启动后通过OPTIONS语句改变的Locale不会影响 ODS输出的消息文本语言。 场景2:ODS输出中的SAS消息文本语言符合当前SAS会话的 Locale,而日志消息为英语。在SAS Unicode Server中此场景比较常用。 要实现此场景,在启动SAS时仅指定选项ODSLANGCHG即可。在 这种情况下,SAS启动后通过OPTIONS语句改变的Locale也会影响ODS 输出的消息文本语言。 8.3 NL格式和NL输入格式 SAS提供了NL格式(format)和NL输入格式(informat),这些格 式能够根据运行的SAS会话的语言/区域来转换日期、日期时间、货币和 数字的格式。这样一来,使用了同样的NL格式和NL输入格式的SAS代 码在不同的语言/区域的SAS会话中运行,就可得到符合对应地区语言文 化习俗的日期、日期时间、货币和数字的表示形式。 NL格式(和NL输入格式)与非NL格式(和NL输入格式)的使用 方式一样,其名称的后缀w或w.d为输出和要读入的格式宽度,在使用时 格式名称中的w和d可指定为数字,也可以省略。省略时则使用SAS定义 的默认值。 1.SAS NL格式 表8.5~8.9给出了常用的NL格式,并展示了它们在不同Locale下的输 出示例。这些示例中,大多数都为省略名称中的格式宽度w或w.d(即使 用默认值)的输出结果。但在有些Locale下,一些格式的结果会超过默 认长度,SAS会自动进行调整,这时可能会产生非预期的结果,需要显 式地指定更长的格式宽度以显示正确格式。 表8.5 日期格式 表8.6 时间和日期时间格式 表8.7 时区格式 注:该格式默认宽度为40,在Locale为zh_CN时,必须指定更大的 长度才可正确显示该值。 表8.8 数字 上述表格给出的是各种格式默认宽度的w和小数位d对数字的输出 格式。除了格式NLBESTw.d和NLPVALUEw.d外,其他格式的小数位 (即d值)默认为0。可以指定d为非0的整数以对数字显示更大的精度, 例如,格式NLNUM8.2和NLPCT7.2会将同样的数字分别显示为2, 356.78和-57.68%(各包含两位小数)。 货币通常会遵循数字格式,但是,货币符号的位置在各个国家可能 会不一样。SAS支持命名形式为NLMNLxxxw.d或NLMNIxxxw.d的各种 货币格式,其中xxx指给定货币的国际编码。表8.9给出了欧元(EUR) 和人民币(CNY)的格式。对于其他货币,例如USD表示美元、AUD 表示澳元,需要时请参考SAS帮助文档。注意,这里仅根据Locale改变 符号和模式,简单地显示了数据中的数字,并不进行汇率换算。 表8.9 货币格式 2.SAS NL输入格式 下面的一系列表给出了常用的NL输入格式,并展示了各种输入格 式在不同Locale下省略其名称中的格式宽度w或w.d(即使用默认值)时 能够读入的日期、日期时间、时间、数字和货币的示例。当输入数据会 超过默认长度时,需要显式地指定更长的格式宽度以正确读入。 (1)日期、日期时间、时间 表8.10给出了关于日期、日期时间和时间的NL输入格式,以及在 Locale为en_US和zh_CN时,这些NL输入格式可以处理的部分日期、日 期时间或时间形式的示例,但实际上这些NL格式能够正确地读入更多 Locale下的不同数据形式。 表8.10 日期、日期时间、时间输入格式 (2)数字 表8.11给出了SAS提供的关于数字的NL输入格式。 表8.11 数字输入格式 (3)货币 在SAS中,货币的NL输入格式与NL格式大致相同,请参考上面关 于的NL货币格式的描述,或者通过SAS帮助文档进行学习。 3.自定义格式 在使用FORMAT过程定义格式时,还可以通过指定选项LOCALE来 定义只能在指定的Locale下可用的格式。通过多个FORMAT过程,则可 定义在不同Locale下使用的不同格式。 例8.3:根据美国、中国的不同身高标准,将sashelp.class中的记录 进行分类并输出。 首先定义两个具有相同名称的格式size.,其中一个为在美国使用的 格式,另一个为在中国使用的格式。因为美国人的身高普遍较高,所以 其对身高进行分类时的标准不一样。定义格式的语法如下: proc format lib=saslib.formats; value size low - 56 = 'Short' 56 - 66 = 'Medium' 66 - high = 'Tall' ; run; options locale=zh_CN; proc format lib=saslib.formats locale; value size low - 50 = '偏矮' 50 - 60 = '中等' 60 - high = '偏高' ; run; 注意 在该代码中,除了涉及身高的分类标准之外,还使用了本 地区的语言。在定义格式时,如果只是想要对不同的地区使用不同的语 言,则可以和SAS程序中的其他文本字符串一起使用字符串外部化机制 (后面会介绍)。 运行该段代码,会在逻辑库saslib的物理路径下生成两个文件,其 名称分别为formats.sas7bcat和formats_zh_cn.sas7bcat。因为在第二个 FORMAT过程中指定了选项LOCALE,所以其文件名带有Locale信息 zh_cn。 接下来按Locale使用逻辑库saslib中的格式size.,代码如下: options fmtsearch=(saslib/locale); proc print data= sashelp.class (obs=5); format height size. ; run; OPTIONS语句对选项FMTSEARCH赋值,表示SAS根据当前会话的 Locale去逻辑库中查找对应的格式文件,找到后使用该格式文件对数据 进行格式化,如果对应Locale的格式文件不存在,则使用不带Locale信 息的格式文件中定义的格式去对数据进行格式化。 当前会话Locale为zh_CN时,PRINT过程的输出如图8.8所示。 当前会话Locale为en_US或其他值时,PRINT过程的输出如图8.9所 示。 图8.8 使用自定义格式(Locale=zh_CN) 图8.9 使用自定义格式(Locale=en_US) 同样,也可以使用FORMAT过程为其他Locale(例如fr_FR)创建 对应的格式,以便在该地区使用。 8.4 字符串和字符处理函数 SAS提供了字符串函数和CALL例程,以使用户能够很容易地处理 字符数据。许多原始SAS字符串函数会假定一个字符的大小是一个字 节,但是这种处理仅适用于SBCS字符集中的数据。在处理DBCS和 MBCS字符集中的数据时,有些函数和CALL例程不能被正确处理并产 生正确结果。为了解决这个问题,SAS引入了一组字符串函数和CALL 例程,用于处理DBCS和MBCS数据中的字符串。这类函数通常以字母K 开始,也称为K函数。 在使用K函数时,需要理解以字节为基础的偏移长度和以字符为基 础的偏移长度。以字节为基础的偏移会假定指定的位置是字符串存储的 字节位置。以字符为基础的偏移则假定指定的位置就是字符串中字符的 位置。在SBCS环境下,一个字节对应一个字符,字节位置也就是字符 位置。在DBCS或MBCS环境下操作DBCS和MBCS字符或字符串时,这 两者存在很大差别。 例如,字符串“赛仕”,每个字符存储为2个字节。当以字节为基础 计算偏移时,“仕”的起始位置为3,而以字符为基础计算偏移 时,“仕”的起始位置为2,如表8.12所示。 表8.12 字节偏移和字符偏移示例 K函数使用以字符为基础的偏移长度,可用于处理SBCS、DBCS和 MBCS(UTF-8)数据。关于SAS支持的函数以及这些函数的使用方 法,请参考SAS帮助文档。 注意 有些原始函数也支持多字节数据的处理,也可以在多字节 环境中使用,例如,UPCASE和LOWCASE。 SAS还提供了NLS宏和宏函数,用于在宏语言中处理DBCS和MBCS 数据。关于宏和宏函数及其详细使用信息,请参考SAS帮助文档。 例8.4:使用SAS提供的函数取出字符串Company_Name的前4个字 符。 下面分别以SUBSTR函数和KSUBSTR函数(K函数)为例来实现此 功能。在Windows操作系统中,在简体中文SAS会话中提交如下代码: data _null_; Company_Name="赛仕软件研究开发(北京)有限公司"; Sub_Name1=substr(Company_Name,1,8); Sub_Name2=ksubstr(Company_Name,1,4); put Sub_Name1=/Sub_Name2=; run; 日志窗口的输出如图8.10所示。 图8.10 K函数操作字符串 上面对Sub_Name1和Sub_Name2的赋值语句分别使用了SUBSTR函 数和KSUBSTR函数。通过SUBSTR函数抽取了从第1个字节开始的8个 字节;通过KSUBSTR函数抽取了从第1个字符开始的4个字符,两个函 数执行结果都为“赛仕软件”。从这里也可以看出,使用K函数更加符合 语言处理习惯,而且不需要知道每个字符存储为多少个字节。 注意 在使用SUBSTR函数时,如果指定的字节数不能正好分开 字符,则会产生截断和乱码,参考如图8.11所示的日志窗口中的代码及 消息。因为使用SUBSTR函数抽取从第1个字节开始的5个字节时,除了 抽取到“赛仕”两个字符外,还抽取了接下来的字符“软”(共占两个字 节)的一个字节,该字节在当前会话编码字符集中没有对应的字符,所 以显示为“?”。 图8.11 字符截断后的输出 8.5 文本字符串外部化 软件产品的本地化要求展示给客户的文本为本地区语言。对于SAS 应用来说,意味着其程序中的文本字符串必须翻译。和使用其他语言开 发的应用程序一样,为了使同一个SAS程序能支持多种语言,需要将代 码中的文本字符串提取出来,并在需要使用该字符串的地方使用其通用 的表示方式,该过程称为字符串外部化(String Externalizatin)[1]。接着 要将所提取出来的这些字符串翻译成需要支持的各种语言,随SAS程序 一起交付。在SAS程序运行时,SAS会根据会话的语言/区域设置自动加 载不同的语言文字。 例8.5:开发SAS程序,保证所有文本在不同的Locale下以对应的语 言显示,包括页眉、页脚、标签等,目前需要支持英语和简体中文。 现有的代码如下: %let user=SBJKUX; %let adate=%sysfunc(today(),nldate.); proc datasets library=saslib nolist; modify order_summary; label Country = "Supplying Country"; label Quantity = "Quantity (in Million)"; label Price = "Total Price"; run; title "Order Summmary by Supplying Country"; footnote "Report generated on &adate by &user"; proc report data=saslib.order_summary; run; 接下来描述如何一步一步将上述SAS代码改写为在不同的Locale下 以对应的语言显示输出。 1.第一步:抽取字符串并生成.smd文件 SAS程序中的所有文本字符串都必须抽取出来,经过转换后并放 在.smd文件中。在.smd文件中,每个文本字符串必须与一个唯一名字 (键名称)关联起来,形成键-值对。键名称和键值之间使用等号(=) 联系起来,等号左侧为键名称,右侧为字符串,即键值。键名称只能包 含ASCII字符,其长度不得超过60个字符。键值中的非ASCII字符必须 为Unicode转义字符。生成.smd文件的过程包括抽取代码中的文本字符 串、对字符串进行翻译,并将非ASCII字符转换为Unicode转义字符。 (1)抽取文本字符串 抽取文本字符串并将其存储在扩展名为smd的文件中。该.smd文件 的文件名在下一步中会用作SAS数据集名称,所以该名称必须符合SAS 数据集名称的命名规范。在该例中,将文件命名为myapp.smd。 在抽取文本字符串时,如果字符串中仅包含文本字符,则直接使用 该字符串;如果字符串中包含变量,则需要使用替换字符串。若字符串 中只包含一个变量,则该变量位置使用%s代替,如果包含多个变量, 则使用%#ns替换。%#1s、%#2s、...、%#ns分别表示在该字符串中出现 的第1、第2、...、第n个替换字符串。 对应本例的myapp.smd文件的内容如下: Country_Label=Supplying Country Quantity_Label=Quantity (in Million) Price_Label=Total Price Report_Title=Order Summary by Supplying Country Report_Fn=Report Generated on %#1s by %#2s 当SAS程序中的文本字符串很多时,推荐使用如 myapp.report.title.ui、myapp.report.footnote.ui、myapp.summary.label1.ui 等这些更能表示字符串使用意义或位置的键名称。此外,还可在.smd文 件中添加注释,注释行以#开始。 如果文本字符串中包含符号%,则使用两个百分号(%%)来表 示。例如,要表示字符串“Increase:98%”,则.smd文件中对应的键-值 对如下: msg_note=Increase: 98%% 在.smd文件中,还可使用宏变量。例如,使用宏变量&osname的代 码如下: pgm3_osname_ui = Operating System: &osname. (2)翻译文本字符串 接下来要将该.smd文件中的每个字符串翻译成需要支持的语言。在 本例中需要翻译成简体中文,翻译时要保持替换字符 串“%#1s”和“%#2s”的位置。翻译后的内容如下: Country_Label=供应国家 Quantity_Label=订单量(百万) Price_Label=总价 Report_Title =按供应国家汇总的订单 Report_Fn=%#2s于%#1s生成此报表 (3)使用Unicode转义字符 因为.smd文件中只能包含ASCII字符,所以不能以ASCII表示的所有 字符都必须使用Unicode转义序列来表示。Unicode转义序列的格式为 \uxxxx,其中xxxx是该字符Unicode编码的十六进制表示形式。回到本例 中,上述翻译后的文件中的中文字符必须转换为Unicode转义序列。 有许多工具可实现该转换,例如JAVA JDK中的native2ascii、文本 编辑器UltraEdit等。SAS提供了KPROPDATA函数和UNCODEC函数来 实现该转换。调用KPROPDATA函数进行转换的宏%SMD2ASCII的代码 如下: %macro SMD2ASCII(inf=,outf=,inencoding=,lrecl=); data _null_; attrib tmp length=$ &lrecl ; infile "&inf" lrecl=&lrecl ; input; file "&outf" lrecl=&lrecl ; tmp = kpropdata(_infile_,"uesc", "&inencoding","ascii") ; put tmp ; run ; %mend SMD2ASCII ; 该宏%SMD2ASCII逐行读取输入文件,将不能被ASCII表示的字符 转换成为Unicode转义序列,再写入输出文件。宏%SMD2ASCII的参数 定义如下: ·inf=指定输入文件名 ·outf=指定输出文件名 ·inencoding=指定输入文件编码 ·lrecl=指定读取输入文件和写入输出文件的记录长度 下面调用宏程序%SMD2ASCII将当前编码为euc-cn的文件 myapp_han.smd转换为Uncode转义字符文件myapp_zh_CN.smd。代码如 下: %SMD2ASCII(inf = c:\sas\data\ch8\smd\myapp_han.smd, outf = c:\sas\data\ch8\smd\myapp_zh_CN.smd, inencoding = euc-cn, lrecl = 300 ) ; 转换后的文件在下一步创建数据集时需要使用,这里要求其文件名 为在原文件名的基础上加上对应的Locale标识符。例如所生成的文件 myapp_zh_CN.smd的内容如下: COUNTRY_LABEL=\u4f9b\u5e94\u56fd\u5bb6 QUANTITY_LABEL=\u8ba2\u5355\u91cf(\u767e\u4e07) PRICE_LABEL=\u603b\u4ef7 REPORT_TITLE=\u6309\u4f9b\u5e94\u56fd\u5bb6\u6c47\u603b\u7684\u8ba2\u5355 REPORT_FN=\u7531"%#2s"\u4e8e"%#1s"\u751f\u6210\u6b64\u62a5\u8868 到这里,已经准备了两个文件,myapp.smd和myapp_zh_CN.smd。 如果需要支持其他语言/地区,则使用上述步骤准备对应的.smd文件。 2.第二步:根据.smd文件创建数据集 SAS提供了宏函数%SMD2DS,用以将在上一步中准备好的.smd文 件中的本地化信息收集到一个SAS消息数据集中。宏函数%SMD2DS在 SAS AUTOCALL逻辑库中,SAS启动时会自动加载,可直接使用。其 基本形式如下: %SMD2DS(DIR=,BASENAME=<,LOCALE=><,LIB=>); 其中: ·DIR=指定所有.smd文件所在的文件夹或目录。 ·BASENAME=指定包含本地化信息的文件名前缀。该名称还用于 命名生成的包含这些本地化信息的SAS消息数据集。 ·LOCALE=指定需要处理的所有Locale列表,各个Locale之间使用 空格隔开。如果不指定,则仅使用默认文件basename.smd来创建SAS消 息数据集。 ·LIB=指定所创建的SAS消息数据集的逻辑库。如果不指定该参 数,则自动将该数据集存储在WORK逻辑库。 在本例中调用宏函数%SMD2DS()的代码如下: %SMD2DS(dir = c:\sas\data\ch8\smd, basename = myapp, locale = zh_CN, lib = saslib); 运行该代码,所创建的数据集saslib.myapp在VIEWTABLE窗口中打 开,如图8.12所示。 图8.12 SAS消息数据集 该数据集中包含4个变量:locale、key、lineno和text。其中locale表 示该行观测中的text适用的locale;key为键名称;lineno为当文本字符串 分为多行时的行号;text为消息文本的Uncode转义序列。 3.第三步:在SAS代码中获取字符串 在SAS应用程序代码中,原先使用字符串的位置可使用SASMSG函 数或SASMSGL函数,且通过键值访问上一步所创建数据集并获取对应 的文本字符串。SASMSG函数会根据当前SAS会话的Locale获取对应的 字符串,其基本形式如下: SASMSG(BASENAME, KEY<, QUOTE | DQUOTE | NOQUOTE><, 替换字符串-1 <, …替换字符串-7>>) 其中: ·BASENAME是消息数据集名称,是上一步中使用%SMD2DS宏函 数创建的。 ·KEY指消息的键,对应于消息数据集中的变量Key。如果指定的键 不存在,则返回键名称。 ·QUOTE|DQUOTE|NOQUOTE分别用于控制是否在消息上加引号、 加单引号和加双引号。不指定该参数时,默认为DQUOTE。上述3个选 项可分别简写为Q、D、N。 ·替换字符串指消息文本中需要替换的字符串,例如%s、%#1s 或%#2s。当存在多个时,需依次给出。最多可支持7个替换字符串。 SASMSGL函数也可以根据Locale值和Key从数据集中获取字符串。 但是它不依赖于当前SAS会话Locale,Locale值必须指定为该函数的参 数。其基本形式如下: SASMSGL(BASENAME, KEY, LOCALE<, QUOTE | DQUOTE | NOQUOTE><, 替换字符串-1 <, …替换字符串-7>>) 其中,LOCALE为POSIX Locale值(例如zh_CN),其他参数的意 义与SASMSG函数中一样。关于SASMSGL函数的使用,这里不再给出 示例。 在使用SASMSG函数从消息数据集中获取文本字符串后,该SAS程 序的代码如下: %let ds=saslib.myapp; %let user=SBJKUX; %let adate=%sysfunc(today(),nldate.); proc datasets library=saslib nolist; modify order_summary; label Country = %sysfunc(sasmsg(&ds, Country_Label, noquote)); label Quantity = %sysfunc(sasmsg(&ds, Quantity_Label, noquote)); label Price = %sysfunc(sasmsg(&ds, Price_Label, noquote)); run; title %sysfunc(sasmsg(&ds, Report_Title, noquote)); footnote %sysfunc(sasmsg(&ds, Report_FN,noquote, '&adate', &user)); proc report data=saslib.order_summary; run; 与原始SAS程序相比较,改动如下: ·%let ds=saslib.myapp; SAS程序中增加该语句,用于指定存储本地化信息的消息数据集。 ·%let adate=%sysfunc(today(),nldate.); 在对宏变量adate的赋值语句中,将使用的格式从date.换成NL格式 nldate.,以便根据当前SAS会话的Locale将当天日期转换为本地格式。 在原来程序代码中,所有直接使用文本字符串的地方都会通过 SASMSG函数从存储有本地化信息的消息数据集中读取。 如图8.13和图8.14所示分别是修改后的SAS程序在SAS会话Locale为 en_US和zh_CN时的执行结果。 图8.13 英语报告(Locale=en_US) 图8.14 中文报告(Locale=zh_CN) 如果在存储有本地化信息的消息数据集中不存在当前SAS会话 Locale,SASMSG函数会使用数据集中的Locale为en_US时的字符串。如 图8.15所示为该SAS程序在SAS会话Locale为fr_FR时运行的结果。其中 页眉、标签都为英语,页脚中文本部分也为英语,但页脚中的日期因为 使用了NL格式nldate.,所以显示为法语。 图8.15 无本地化消息的Locale下的输出(fr_FR) [1] 未被外部化的字符串,称为硬编码字符串(Hard-coded String)。 本章小结 8.6 前面介绍了SAS提供的NLS特性,知道它是用于开发多语言支持的 SAS应用程序的。在理解了选项LOCALE=、ENCODING=的基础上,开 发多语言支持的SAS程序时需要注意以下几个方面: ·程序中嵌入的文本字符串。在需要时将嵌入的文本字符串外部 化。 ·级联字符串。级联是将两个或多个文本字符串组合成一个字符串 的操作。但是各种语言的语法结构可能会不一样,级联可能会产生语法 问题,最好避免对要翻译的字符串进行级联。 ·字符串处理函数。在需要使用字符串处理函数时,使用对应的K函 数。如果需要使用对字符串进行操作的宏或宏函数,也使用对应的NLS 宏和宏函数。 ·在需要输出日期、时间、日期时间、数字和货币时,使用可以随 着Locale改变的NL格式。而输入格式需要根据要读取的数据的形式指 定。 如果SAS会话编码为DBCS或MBCS,则检查PUT和INPUT语句中的 列指针。列指针会假定每个字符显示为一列。但是在DBCS或MBCS编 码中,一个字符不等于一个字节或一列,这时可能会引起SAS列指针控 制返回未知的结果。 第二篇 SAS统计分析和时间序列预测 第9章 描述性统计分析 第10章 参数估计与假设检验 第11章 方差分析 第12章 主成分分析与因子分析 第13章 聚类分析 第14章 判别分析 第15章 回归分析 第16章 LOGISTIC回归分析 第17章 时间序列分析 第18章 SAS数据挖掘的一般流程 第9章 描述性统计分析 统计学是关于数据资料的收集、整理、分析和推断的科学,包括描 述性统计学和推断统计学两个基本组成部分。描述性统计学研究如何收 集反映客观现象的数据,并对所收集的数据进行加工处理,然后以适当 的形式进行展示,进而通过综合概括与分析得出反映客观现象规律性的 数量特征。内容包括统计数据的收集方法、数据的加工处理方法、数据 的显示方法、数据分布特征的概括和分析方法等。推断统计学是研究如 何根据已有的数据去推断更多数据的数量特征的方法,它是在对已有数 据进行描述性分析的基础上,对更多数据的数量特征在一定的概率范围 内做出的推断。 统计研究过程的起点是统计数据,终点是探索出客观现象内在的数 量规律性。如果整理搜集到的数据是总体数据,如人口普查数据,则经 过描述性统计分析之后就可以达到认识总体数量规律性的目的了;如果 所获得的数据只是研究总体的一部分数据,如某种药品的试验效果数 据,要找到总体的数量规律性,则必须应用概率论的理论,并根据样本 信息对总体进行科学的推断。 描述性统计学和推断统计学的划分,反映了统计方法发展的前后两 个阶段,同时也反映了应用统计方法探索客观现象数量规律的不同过 程。本书第二篇主要介绍描述性统计学和推断统计学的基本概念和原 理,并结合实例介绍如何使用SAS进行描述性统计分析和推断统计,以 发现客观规律。 描述性统计是整个统计学的基础,若没有描述统计收集可靠的统计 数据并提供有效的分析和处理,即使再科学的统计推断方法也难以得出 切合实际的结论。本章主要介绍统计学中的一些基本概念和各种描述性 统计量的计算方法,以及如何运用SAS中PROC步进行描述性统计量的 计算。 基本概念 9.1 统计学中的概念比较多,为了便于读者对以后各章节的内容进行学 习,这一节里集中介绍常用的基本概念。 9.1.1 总体、个体和样本 总体、个体和样本是统计学中3个最基本的概念。我们称研究对象 的全体为总体(Populations);称组成总体的每个单位为个体或者观 测;从总体中随机抽取n个观测,则称这n个观测是容量为n的样本 (Sample)。例如,在研究某银行所有客户的平均收入时,该银行客户 的全体就是总体,每个客户就是个体。 为了研究该银行的所有客户的平均收入,最精确的办法就是把每个 客户的收入都调查一遍,然而,调查所有客户的收入需要花费大量的人 力、物力和财力,因此,更加现实的做法是从中抽取一部分,比如,随 机选取n个客户(即n个个体)进行调查,然后通过这n个客户的平均收 入去推断整个银行全部客户的平均收入。在这里,这n个个体就是总体 (该银行的所有客户)的一个样本。 9.1.2 简单随机抽样 由于要根据样本的信息对总体进行推断,因此在进行抽样时就有一 定的要求了——要求每次抽取都必须是随机的、独立的,这样才能较好 地反映总体的情况。所谓随机的,是指每个个体被抽到的机会是均等 的,这样抽到的个体才具有代表性。所谓独立的,是指每次抽取之后不 能改变总体的成分。基于这种思想的抽样方法称为简单随机抽样。 9.1.3 连续变量和分类变量 在取得了样本之后,为了选择合适的统计方法,必须明确分析的数 量指标是什么类型的变量。数量指标根据取值的特征可分为连续变量 (Continues Variable)和分类变量(Categorical Variable)。 连续变量指的是取值在两个数值之间可以取任意值的变量,例如气 温,可以是22.34℃或者22.98℃,或者是-10℃~40℃的任何数值。不是 所有的数值变量都是连续变量,例如年龄,取值只能是整数。对于这类 只能取整数值,但取值和连续变量有线性关系的变量,我们通常也当作 连续变量来处理。 分类变量的取值只能是有限个,可以是数值,也可以是字符,通常 表现为互不相容的类别或者属性。分类变量根据取值的特征可以分为定 类变量(Nominal Variable)和有序变量(Ordinal Variable)。定类变量 是指所有类别或者属性之间无程度和顺序的差别,如性别(男、女), 药物反应(阴性、阳性),血型(O、A、B、AB)。有些定类变量的 取值也可以是数值,例如,性别可以用0和1表示,0代表男性,1代表女 性,这时0和1之间没有顺序大小的关系。和定类变量不同的是,有序变 量的取值之间存在顺序关系,但是两个取值之间的“距离”不易量化,例 如,饮料杯尺寸(小、中、大),反应(非常不同意、不同意、同意、 非常同意)。 9.1.4 参数、统计量和自由度 参数是描述总体特征的数量指标。例如,上面所提到的某银行所有 客户的平均收入,某工厂某个批次产品的合格率等。由于总体数据通常 是未知的,因此,参数往往是一个未知数。与参数所对应的是我们通常 提及的统计量,统计量是描述样本特征的数量指标,例如样本的均值和 方差等。统计量是根据样本数据计算得出的,随着样本的不同而有所差 异,所以它是关于样本的函数。用由样本数据计算出来的统计量来估计 相应总体的参数,是推断统计的重要内容。 自由度是指某一统计量中变量可以自由取值的个数,通常用DF表 示。假设某个统计量中的变量X共有10个取值,分别为x1,x2,…, x10,则DF=10。如果变量X的n个取值受到k个条件的制约,则DF=n-k, 例如,如果有约束条件∑xi=195,则DF=9。 例如,欲了解某市的中学教育情况,那么该市的所有中学则构成了 一个总体,其中每一所中学都是1个个体。从全市中学中随机抽取了10 所中学,则这10所中学就构成了一个样本。在这项调查中,若主要考察 的是升学率,那么升学率就是一个变量。若做这项调查的目的是了解全 市的平均升学率,那么升学率的平均值就是一个参数。而用样本的升学 率计算出来的平均升学率就是一个统计量。由于随机抽取的这10所中学 的升学率之间相互没有依赖关系,即可以自由取值,所以统计量平均升 学率的自由度DF=10。 9.1.5 随机变量及概率分布 在自然界和社会上发生的现象是多种多样的,有一类现象,在一定 条件下必然发生,例如,向上抛石子必然落下,同性电荷必相互排斥, 这类现象称为确定性现象。同时,自然界和社会上还存在着另一类现 象,例如,在相同条件下抛同一枚硬币,其结果是可能正面朝上,也可 能是反面朝上,并且每次抛掷之前无法肯定抛掷的结果是什么。这类现 象,在一定条件下,可能出现这种结果或那种结果,但是在大量的重复 试验下,它的结果又呈现出固定的规律性,例如,多次重复抛掷一枚硬 币得到正面朝上的情况大致有一半。这种在个别试验中其结果呈现出不 确定性,但在大量重复试验中其结果又具有统计规律性的现象,称为随 机现象。我们通过随机试验和随机变量来研究随机现象,揭示其统计规 律性。 一个随机试验的可能结果的全体组成一个基本空间Ω,随机变量X 是以Ω为定义域、取值为实数的函数。通俗地说,随机变量就是随机现 象中随机试验结果的函数。例如,投掷一枚骰子的所有可能结果是出现 1点至6点,则{1点,2点,3点,4点,5点,6点}就是一个基本空间,若 定义X为投掷一枚骰子时出现的点数,则X为一随机变量,当投掷结果 出现1点、2点、3点、4点、5点和6点时,X的取值分别为1、2、3、4、 5和6。可见,随机变量的取值是随试验的结果而定的,且在试验中各个 结果的出现有一定的概率,因而随机变量的取值也有一定的概率。概率 分布就是用来描述随机变量取值的概率规律的函数。 如果随机变量的取值是离散值,则为离散型随机变量,描述离散型 随机变量的概率分布可使用分布列的方式,即给出离散型随机变量的全 部取值,以及取得每个值的概率。常见的离散型随机变量的概率分布有 单点分布、两点分布、二项分布、几何分布、超几何分布、泊松分布 等。 如果随机变量的取值是连续值,则为连续型随机变量,连续型变量 的取值范围为某个有限区间[a,b]或(-∞,+∞)。连续型随机变量的概 率分布函数计算的是随机变量X小于任意已知实数a的概率,表示为 FX(a)也称为累积分布函数(Cumulative Distribution Function, CDF);p(x)称为概率密度函数(Probability Density Function, PDF)。概率密度函数体现的是随机变量取某一个值的概率规律。对累 积分布函数进行求导,可以得到概率密度函数。常见的连续型随机变量 的概率分布有均匀分布、正态分布和指数分布等。 1.伯努利试验和二项分布 最简单的随机试验是只有两种试验结果的随机试验,也称伯努利试 验。如抛一枚硬币,要么正面朝上,要么反面朝上;检查一个产品的质 量,要么合格,要么不合格。一般地,把这两种试验结果分别看作“成 功”和“失败”,用数值1和0表示,定义一次伯努利试验成功的次数为随 机变量X,则X的概率分布是一个最简单的分布类型,即两点分布。伯 努利试验是一种非常重要的概率模型,历史上,它是最早得到研究的概 率模型之一,也是得到最多研究的概率模型之一,在理论研究上具有重 要的意义,概率论中著名的大数定律也是建立在其基础之上;同时,它 有着广泛的实际应用,例如在工业产品质量检查中。 若将伯努利试验重复n次,n是一个固定的数值,则称为n重伯努利 试验。在n次试验中,每次成功的概率为p,成功的次数对应于离散型随 机变量X,则X的概率分布就是一个二项分布(Binomial Distribution)。需要强调的是,在n重伯努利试验中,每次试验成功的 概率都是一样的,并且是相互独立的。记n重伯努利试验中成功k次的概 率为b(k;n,p),计算方法如下: 为了对二项分布有个直观了解,图9.1中分别作出了p=0.1、p=0.3、 p=0.5时二项分布的分布曲线,横坐标是成功的次数k,纵坐标是对应k 的概率。在图9.1中,最左边的一条曲线为p=0.1的二项分布,中间一条 曲线代表p=0.3的二项分布,最右边的一条曲线代表p=0.5的二项分布。 从图中可以看出,对于固定的n和p,当k增加时,b(k;n,p)先增 加,在达到某个极大值后,再下降。p越接近0.5,分布曲线形状越对 称。 图9.1 二项分布概率密度曲线 2.泊松分布(Poisson Distribution) 泊松分布是二项分布的近似分布,它由法国数学家泊松引入。在很 多应用问题中,我们常常遇到这样的伯努利试验,n大,p小,而乘积 λ=np大小适中,对于这类情况,就可以将二项分布转化为泊松分布来计 算。 泊松分布的概率密度函数为 ,其中λ为参数。泊松分布常用来 描述单位时间内随机事件发生的次数,λ表示单位时间内(单位面积 内)随机事件的平均发生率。在自然界中,很多分布都服从泊松分布, 例如社会生活中,一定时间内电台中得到的呼叫数,一定时间内到达某 公共汽车站的乘客数等都服从泊松分布,因此,在运筹学及管理科学中 泊松分布占有重要地位。在物理学中,放射性分裂落到某区域的质子的 点数、显微镜下落到某区域中的血球或微生物的数目都服从泊松分布。 在二项分布的应用中,当p相当小时(p≤0.1), 。 图9.2是对应于不同参数λ的泊松分布图,横轴代表随机事件发生的 次数k,纵轴代表概率。4条曲线从左到右依次为pdf_1、pdf_2、pdf_3和 pdf_6,分别代表了λ=1,2,3,6时的泊松分布的概率密度曲线。 3.正态分布(Normal Distribution) 若连续型随机变量的概率密度函数 ,-∞<x<+∞,其中σ>0,μ 与σ均为参数,则该连续型随机变量服从正态分布,记为N(μ,σ2)。 特别地,当μ=0,σ=1时,该分布为标准正态分布,记为N(0,1)。任 何正态分布都可以转化成标准正态分布。正态分布是自然界中最常见的 一种分布,例如测量的误差,炮弹落点的分布,人的生理特征如身高、 体重等,农作物的收获量,工厂出产的产品尺寸如直径、长度、宽度、 高度等,都近似服从正态分布。一般来说,若影响某一数量指标的随机 因素很多,而每个因素所起的作用都不太大,则这个指标服从正态分 布。正态分布具有许多良好的性质,许多分布可用正态分布来近似,如 二项分布,另外一些分布又可以通过正态分布来导出,如T分布、 Gamma分布、F分布等。因此,正态分布在理论研究方面也有着非常重 要的作用。 图9.2 泊松分布概率密度曲线 一般正态分布的密度函数p(x)的曲线如图9.3所示,图中的3条曲 线pdf_1、pdf_25和pdf_5表示N(0,1),N(0,0.252)及N(0, 0.52)的分布形状。不难看出,正态分布具有很好的性质,p(x)在 x=μ处达到最大,整个分布曲线关于x=μ对称,当σ不同时,p(x)的形 状也不同,当σ越小时,分布越集中在x=μ附近,当σ越大时,分布越平 坦,这里μ=0。 若随机变量X服从正态分布N(μ,σ2),则 P{|X-μ|<σ}≈68.27% P{|X-μ|<2σ}≈95.45% P{|X-μ|<3σ}≈99.73% 图9.3 正态分布概率密度曲线 也就是说,若随机变量服从正态分布,则有68%的取值落在区间 (μ-σ,μ+σ)中,有95%的取值落在区间(μ-2σ,μ+2σ)中,而几乎 99.7%的取值是落在(μ-3σ,μ+3σ)之中,如图9.4所示。这也是质量管 理中著名的6Sigma管理方法的理论基础。 图9.4 正态分布累计概率示意图 二项分布、泊松分布和正态分布是概率论中最重要的3个分布,在 本书后面的介绍中会应用到。除此之外,常见的离散型概率分布还有几 何分布、超几何分布、巴斯卡分布等,常见的连续型概率分布还有均匀 分布、指数分布等,这里不详细介绍,读者有兴趣可以参考复旦大学出 版的《概率论基础》一书。 描述性统计量 9.2 抽取了合适的样本后,在利用样本数据对总体进行推断统计之前, 有必要对样本数据进行探索。一方面,能够及时发现样本数据中存在的 问题,例如数据缺失的比例,特殊值的存在;另一方面,也可以观察数 据的分布是否大致服从某种分布。这样,有利于提高后期进行推断统计 分析的准确性。描述性统计主要分为三大类:第一类是描述数据集中趋 势,如计算均值、中位数、众数等统计量;第二类是描述数据离散程 度,如计算方差、标准差、变异系数等统计量;第三类是描述数据分 布,如计算偏度系数、峰度系数、百分位数、直方图等。 9.2.1 描述数据集中趋势 数据的集中趋势指的是数据的取值从两边向中间集中,常用来反映 数据的一般水平,常用的统计量有均值、众数和中位数等。 例如,一组样本的取值分别为x1,x2,…,xn,均值用x表示,则均 值的定义为 均值是衡量数据中心位置的重要统计量,反映了一些 数据必然性的特点。均值由全部数据计算得出,包含了全部数据的信 息,具有良好的数学性质。当数据的各个取值之间的差异程度较小时, 用均值可反映数据的一般水平,具有较好的代表性。 中位数是另一种反映数据中心位置的统计量,其计算方法是将所有 数据按从小到大的顺序排列,如果有奇数个数据值,则位于中央的数据 值就是中位数,如果有偶数个数据值,则中位数为位于中央的两个数据 值的平均。众数是指在数据中发生频率最高的数据值,是一组数据分布 的峰值。 例如,一次考试中,班级12个同学的分数如下:98、42、92、90、 47、55、85、81、63、79、95、70。这组数据的均值,也就是班级的平 均分数为这12个数加起来除以12,为74.75。将这组数据按从小到大排 列,如表9.1所示。 表9.1 按分数大小排列的学生成绩表 排在中间位置的是79和81,中位数为80。 再如一组样本的取值为1、2、1、2、1、3,这组样本中数据值1出 现了3次,数据值2出现了2次,数据值3出现了1次,所以众数为1。 对定类变量进行分析,并且变量的取值种类不多时,运用众数代表 一般水平比较合理。例如,某公司对未来的4个搬迁地址进行了民意调 查,从返回的100份调查问卷中,答案中有60人选择了A地,20人选择 了B地,15人选择了C地,5人选择了D地。众数是A地,代表了公司大 部分员工的意见。 由于中位数的大小仅与数据的排列相关,因此中位数不受数据中个 别极端值的影响,有时更具有代表性。例如分析一个有10户居民的小区 居民收入水平时,正好有一个年薪逾千万的富翁居住于该小区,而其余 都是年薪在10万元左右的普通工薪阶层,如果用均值来代表这个小区居 民的平均收入水平,那么这个小区居民的平均年收入都超过了百万元, 但如果用中位数来衡量,就可以体现这个小区至少有一半居民都是普通 工薪阶层。这是因为富翁与其余居民的收入差距太大,导致均值不能代 表一般水平。 在实际应用中,很难给出一个定律来告诉大家在什么时候适合使用 均值、中位数或者众数来代表一般水平,通常只能根据数据的大致分布 状况并结合其他的统计量来一起解读数据。如果一组数据服从正态分 布,那么数据的均值、中位数和众数都相等。 运用MEANS过程可以计算数值型变量的均值、中位数和众数等描 述性统计量。MEANS过程的基本用法为: PROC MEANS <DATA=数据集><统计量关键字选项其他选项>; VAR 变量1 <变量2 …>; RUN; 其中,统计量关键字选项可以用来指定需要输出的统计量,其他选 项可以用来指定统计量的输出格式等,VAR语句指定分析变量。当省略 数据集时,系统默认分析最近生成的数据集;当省略统计量关键字选项 时,系统默认按顺序输出频数(N)、均值(MEAN)、标准差 (STD)、最大值(MAX)和最小值(MIN)5个统计量;当省略VAR 语句时,系统默认分析数据集中的所有数值型变量。 例9.1:sashelp.fish是包含了一个湖泊里各种鱼类身长、重量、宽度 等特征的样本,每条观测代表一条鱼的数据,总共有7个变量,变量 Species表示鱼所属的种类,Weight表示鱼的重量,Length1、Length2、 Length3分别表示3种测量方法下鱼的身长,Height表示鱼的宽度,Width 表示鱼的厚度。现在要计算样本中各个数量指标的均值、中位数和众 数。 示例代码如下: proc means data=sashelp.fish mean median mode maxdec=2; title "Descriptive Statistics of Tendency"; var weight length1 length2 length3 height width; run; 输出内容如图9.5所示。 图9.5 例9.1输出内容 上面的代码指定输出了均值(MEAN)、中位数(MEDIAN)、众 数(MODE)3个统计量,并指定了每个统计量的数值只能带两位小 数。 9.2.2 描述数据离散程度 数据的离散程度分析主要是用来反映数据之间的差异程度的,不同 的数据类型有不同的计算方法。均值是一个代表性数值,反映数据取值 的一般水平,它把数据各取值之间的差异抽象化了。但数据各取值之间 的差异是客观存在的,这种差异也是数据的重要特征之一,反映了数据 的均衡性和稳定性。因此,要全面反映一个数据的特征,必须测定数据 的差异程度,常用的变异指标有标准差、方差、变异系数、极差、四分 位数极差等。 1.标准差和方差 设总体的均值为μ已知,总体大小为N,则总体标准差σ为 当总体均值μ和总体大小N都未知时,而样本均值x和样本容量n已 知时,则样本标准差s为 标准差的平方,称为方差。 2.变异系数 变异系数是一种不受单位影响的表示数据离散趋势的指标,特别适 合于在两种情况下(各组数据的单位不完全相同时,或者各组数据间的 均值相差悬殊时)比较各组间变异程度的大小。用CV表示其形式如 下: 标准变异系数是标准差与均值之比。它可以用来对比不同水平数据 的均值所具有的代表性。当标准变异系数小时,均值的代表性大,反 之,均值的代表性不大。 以上这些变异指标在数据呈正态分布或近似正态分布的时候,能够 比较好地代表数据的变异程度。若数据呈现明显的偏态分布时,则极差 和四分位数极差具有比较好的代表性。 3.极差(RANGE) 极差是数据的最大值与最小值之差,也称全距,计算方法简单、易 懂,但是极易受极端数据的影响,不能全面反映所有差异及分布状况, 准确程度差,只能起到粗略地描述数据离散趋势的作用,故应用得较 少。 4.四分位数极差 四分位数极差是数据中上分位数和下分位数之差,在实际应用中, 分位数更多地是用来描述数据的分布情况。而四分位数极差,则用来度 量呈偏态分布的数据的离散程度。下面在介绍描述数据分布形态时,会 具体介绍分位数、上分位数和下分位数的概念及计算方法。 例9.2:接着例9.1计算样本中各指标的标准差、方差、变异系数、 四分位数极差,并回答不同种类鱼的重量的均值是否存在差别。 示例代码如下: proc means data=sashelp.fish mean std var cv range qrange; title "Descriptive Statistics of Dispersion"; var weight length1 length2 length3 height width; run; proc means data=sashelp.fish mean; title "Descriptive Statistics Using PROC MEANS"; var weight; class species; run; 第一段程序中,指定输出了统计量MEAN、标准差(STD)、方差 (VAR)、变异系数(CV)、极差(RANGE)和四分位数极差 (QRANGE)。输出如图9.6所示。 图9.6 例9.2第一个MEANS过程输出内容 第二段程序中,运用了CLASS语句,系统将按CLASS语句中指定 的分类变量进行分类,对每个类别分别计算均值统计量。输出内容如图 9.7所示。 图9.7 例9.2第二个MEANS过程输出内容 观察报表可以发现,不同种类的鱼在重量方面存在较大的差别。 在进行描述性统计分析时,如果需要按分类变量进行分组,除了可 以使用CLASS语句以外,也可以使用BY语句。使用BY语句时,系统在 计算描述性统计量之前也会先将数据集中的数据按照BY变量的取值进 行分组,然后再分别计算描述性统计量。使用BY语句的语法如下: BY<DESCENDING>变量1<<DESCENDING>变量2><NOTSORTED>; 如果在BY语句中没有使用选项NOTSORTED,在使用BY语句前, 数据集必须按照BY变量排序或者建立索引。在使用选项NOTSORTED 时,数据集不需要按BY变量排序,系统默认数据集中每个BY组合按观 测号是连续的,如果数据集中每个BY组合取值相同的观测不按观测号 连续排序,则系统认为它们分别是不同的BY组合。 BY语句和CLASS语句的作用类似。不同的是,在使用BY语句时, 输出结果会按照BY变量不同的取值输出到不同的报表中,而使用 CLASS语句时,输出结果是在同一个报表中的,并且不要求数据是按分 类变量排过序的。 此外,默认情况下,CLASS语句中的任何分类变量为缺失值时, MEANS过程会自动将分类变量为缺失值的观测从统计中删除;如果在 PROC MEANS语句中使用选项MISSING,则系统将会认为缺失值是分 类变量的一个类别,在统计时单独将其作为一类列出。 BY语句和CLASS语句中都可以指定多个BY变量或分类变量进行交 叉分析。在本章后面的部分和接下来的其他章节中,讲到PROC步时, CLASS语句和BY语句的用法都和MEANS过程中类似,将不赘述。 9.2.3 描述数据分布形态 在进行统计分析时,通常需要假设样本服从某种分布。所以在进行 分析之前有必要对数据的分布形态进行初步的了解,检查数据是否大致 服从某种分布,然后再运用统计理论去进行假设检验。描述数据分布形 态有两种基本方法,一种是计算统计量,一种是作图。 1.百分位数 百分位数是一种位置指标。在一组数据中,百分位数体现的是有百 分之多少的数据大于该分位数。例如,在整个数据中有60%的数据大于 第40百分位数,同样有40%的数据小于等于第40百分位数。由此可见, 前面讨论的中位数其实就是第50个百分位数。第25个百分位数和第75个 百分位数分别叫做下分位数和上分位数,前面描述数据离散程度的变异 指标“四分位数极差”等于上分位数和下分位数之差。 在计算百分位数时,都要先将观测按照从小到大的顺序排列,排列 后的数据以x1,x2,x3,…,xn表示,n代表非缺失的观测个数。欲求出 第t个百分位数Pt,首先必须找出百分位数在排序数据中的相对位置j和 g,j和g的定义如下: j+g=n×t/100 在上面公式中,j是 的整数部分,g是 的小数部分。 当g=0时,pt=(xj+xj+i)/2;当g>0时,pt=xj+i。 以某班12个同学的考试成绩为例,将12个同学的成绩按从小到大排 列,计算第25个百分位数时,j=3,g=0,则第25个百分位数为 (63+55)/2=59,表示有25%的同学考试成绩低于59分,如图9.8所示。 图9.8 百分位数和极值计算示意图 在SAS中,系统提供了5种计算百分位数的方法,主要区别在于当 找到百分位数在排序数据中的相对位置后,如何对相邻的两个数据进行 加权处理。这里介绍的方法是SAS默认的计算方法,如果读者有兴趣可 以参考SAS帮助文档查看其他4种计算方法。 2.盒状图(Box Plot) 盒状图可将数据的百分位数、均值和极值等统计量通过图形展示出 来,进而帮助分析数据的分布。 在盒状图中,距离盒子的底部(第25个百分位数)或顶部(第75个 百分位数)超过1.5IQR的数据点都用圆圈表示。在如图9.9所示的盒状 图中,中位数和均值基本重合,可见变量基本是对称分布的。 图9.9 盒状图 3.直方图(Histogram) 直方图是一种几何形图表,它可以将收集到的看似无序的数据进行 处理,反映数据的基本分布情况。它根据数据的分布情况,画成以组距 为底边、以频数或百分比为高度的一系列连接起来的直方型矩形图,每 个矩形图代表了一组数据,矩形的高度代表落在这一组中的数据的频数 或者百分比,如图9.10所示。 图9.10 直方图 在上面的直方图中,大部分数据都落在了靠近均值的中间的分组 中,并且直方图基本呈对称分布,和我们前面介绍的正态分布的特征类 似。 由于正态分布具有很好的性质,因此在对数据的分布形态进行分析 时,通常都会和正态分布进行比较。下面来看几个分布,图9.11中的直 方图表示数据的分布,曲线表示以该数据的均值和标准差为参数作出的 正态分布的密度曲线。可以看到,和正态分布相比较,它们有的偏向于 数据小的一侧,有的偏向于数据大的一侧,有的在尖峰处异常陡峭,有 的在中间位置凹进去了。直方图的不同形态代表了数据分布的不同特 征。 图9.11 常见数据分布图 4.偏度系数和峰度系数 统计量偏度系数(Skewness)和峰度系数(Kurtosis)正是用来刻 画数据的分布形态的。偏度系数和峰度系数其实都是和正态分布相比较 而言的,偏度系数用来描述分布是对称分布还是偏向某一侧,峰度系数 用来描述分布是向中心位置集中还是向两侧集中。 设样本均值为x和样本容量为n,样本标准差为s,偏度系数的计算 公式如下: 如果计算得出的一组数据的偏度系数接近于0,则数据大致呈对称 分布,例如正态分布的偏度系数为0。当偏度系数小于0时,若和正态分 布向比较,数据分布偏向数据小的一侧,数据的均值小于中位数,称数 据呈负偏态分布或偏左分布;当偏度系数大于0时,若和正态分布相比 较,数据分布偏向数据大的一侧,数据的均值大于中位数,称数据呈正 偏态分布或偏右分布。 峰度系数的计算公式如下: 当峰度系数<0时,分布称为低峰分布(Platykurtic Distribution)。 如果分布也是对称的,则相较于正态分布,数据呈现出:较少的数据会 分布在两段尾巴处,而且分布有时会具有较平坦的峰部,因此这样的分 布也通常称为薄尾分布(Light-Tailed Distribution)。 当峰度系数>0时,分布称为尖峰分布(Leptokurtic Distribution)。 如果分布也是对称的,则相较于正态分布,数据呈现出:较多的数据会 趋向于分布在两段尾巴处,而且分布有时会具有较陡峭的峰部,因此这 样的分布也通常称为厚尾分布(Heavy-Tailed Distribution)。 正态分布的峰度系数为0。统计研究表明,大部分权益类金融产品 的回报率呈现出尖峰厚尾的特征。 非对称分布的峰度系数通常也是非零数,这时通过峰度系数就较难 判断数据的分布形态了。 为了让读者对于偏度系数和峰度系数及对应的分布形态有更直观的 印象,下面给出了几种典型分布的直方图和偏度系数及峰度系数特征, 供读者参考。 如图9.12所示的这组数据通过计算得出偏度系数为0.003291,接近 于0,可以知道数据基本呈对称分布;峰度系数为-0.01685,接近于0, 没有尖峰厚尾或者峰部平坦的特征。此外,直方图和正态分布的密度曲 线非常接近,则可以近似地认为该组数据服从正态分布。在后面的章节 中,还会介绍如何用假设检验的理论检验某组数据是否服从正态分布。 图9.12 正态分布直方图 如图9.13所示的这组数据的偏度系数为1.005005>0,呈现出正偏态 分布的特征,相较于正态分布,较多的数据偏向数据大的一侧。由于是 非对称分布,峰值也通常不会接近于0。 图9.13 正偏态分布直方图 如图9.14所示的这组数据的偏度系数为-0.97685,呈现出负偏态分 布的特征,相较于正态分布,较多的数据分布在数值较小的一侧。 图9.14 负偏态分布直方图 如图9.15所示的这组数据的偏度系数为-0.01365,接近于0,分布基 本对称,峰度系数为2.318803,因此较多的数据分布在中间位置,呈现 出尖峰厚尾的特征。 图9.15 尖峰分布直方图 如图9.16所示的这组数据的偏度系数为0.015395,接近于0,分布基 本对称,峰度系数为-1.80003<0,因此相较于正态分布,较多的数据分 布在两侧,峰部平坦甚至低凹。 图9.16 低峰分布直方图 5.正态概率图 正态概率图是用于检查一组数据是否服从正态分布的图形,是实际 数据与正态分布分位数之间函数关系的散点图。如果一组数据服从或者 接近正态分布,则正态概率图将是一条直线。在正态概率图中,横轴表 示标准正态分布的分位数,纵轴表示实际数值。下面来看一个正态概率 图,如图9.17所示。其中的圆圈是以标准正态分布的分位数为横坐标, 以实际数值为纵坐标作出的散点,大多数的圆圈基本落在拟合的直线 上,也说明该数据分布接近正态分布。 图9.17 正态概率图 以下给出几种典型分布的正态概率图,如图9.18~9.21所示。读者有 兴趣可以参考SAS帮助文档深入研究。 图9.18 正偏态分布概率图 图9.19 负偏态分布概率图 图9.20 尖峰分布概率图 图9.21 低峰分布概率图 图9.20显示该数据分布呈现出尖峰厚尾的特征。 如图9.21所示,相较于正态分布,该数据分布的峰部比较平坦。 如果横坐标不是正态分布的分位数,而且是其他任一已知分布的分 位数,则该图形统称为概率图。通常,概率图可以用于检查一组数据是 否服从任一已知分布,如二项分布或者泊松分布。 6.UNIVARIATE过程 UNIVARIATE过程是探索性数据分析中最常用的过程之一,与 MEANS过程一样可以计算均值、中位数、众数、百分位数、偏度系 数、峰度系数等描述性统计量。但除这些以外它还可以绘制直方图,能 更加直观地给出变量的分布情况。运用UNVIRIATE过程的一般语法如 下: PROC UNIVARIATE DATA=数据集选项; VAR 分析变量; HISTOGRAM 分析变量</选项>; PROBPLOT 分析变量</选项>; INSET 统计量关键字</选项>; RUN; 其中: ·VAR语句用来指定分析变量,若省略VAR语句,系统将分析数据 集中所有数值型变量。 ·HISTOGRAM语句可以针对指定的变量绘制直方图,方便探索变 量的分布。HISTOGRAM语句中可以使用选项NORMAL作出正态分布 的密度曲线,便于比较分布是否接近正态分布。 ·PROBPLOT语句可以指定作出概率图,比较数据是否服从某一已 知分布,包括正态分布、二项分布、泊松分布等。 ·INSET语句可以在UNIVARIATE过程中作出的图形上标注统计量 计算值。INSET语句必须紧跟在作图语句CDFPLOT、HISTOGRAM、 PPPLOT、PROBPLOT或者QQPLOT后面。 例9.3:查看数据集sashelp.fish中种类为Bream的鱼类的宽度分布是 否接近正态分布。 示例代码如下: proc univariate data=sashelp.fish plot ; where species="Bream"; title"Descriptive Statistics Using ProcUnivariate"; var height; histogram /normal(mu=est sigma=est) kernel; insetskewness kurtosis/ position=ne; run; 在上述程序中,选项PLOT产生了平行条状图、盒状图和正态概率 图。HISTOGRAM语句中使用了选项NORMAL,使得系统作出指定均 值和标准差的正态分布的密度曲线,选项MU=指定了正态分布的均值, SIGMA=指定了正态分布的标准差;选项KERNAL使得系统基于数据的 分布作出了核分布的密度曲线,我们可以简单地将该曲线看成直方图的 光滑版,便于和正态分布的密度曲线进行比较。接下来来分析 UNIVARIATE过程的输出报表,如图9.22和图9.23所示。 图9.22 例9.3输出描述性统计量等报表 图9.23 例9.3输出分位数和极值观测报表 通过分析报表,我们发现: ·该数据的均值为15.18321,和中位数14.95440很接近,这说明 Bream种类的鱼的宽度分布基本上是对称的。 ·偏度系数为0.2417,说明该分布有轻微右偏的趋势。 ·峰度系数为-0.5914,说明相较于正态分布,分布的峰部较为平 坦,没有厚尾特征。 ·宽度最小的观测是第1条观测,宽度为11.52,宽度最大的观测是第 30条观测,宽度为18.9570。 这里的百分位数的计算方法是SAS系统默认的算法,用户也可以在 PROC UNIVARIATE语句中运用选项PCTLDEF=来指定其他4种方法中 的一种。 从正态概率图可以看到,散点基本集中在拟合的直线周围,说明该 分布比较接近正态分布,如图9.24所示。 从直方图中可以看出,大约45%的数据都集中在中间两个矩形中, 并且在图形的右上角插入了偏度系数和峰度系数。密度曲线和正态分布 的曲线也比较接近,显示了该数据近似服从正态分布,如图9.25所示。 上面只是从图形和描述性统计量上得知了分布的大致特征, UNIVARIATE过程还对分布的正态性作出了检验,如图9.26所示。这里 的数据表明,这个湖里的Bream这种鱼的宽度特征服从均值为15.18,标 准差为1.96的正态分布。在第10章中将对假设检验的基本理论进行讲 解。 图9.24 例9.3中选项PLOT所作图形 图9.25 例9.3中HISTOGRAM语句所作直方图 图9.26 例9.3中正态分布检验报表 9.3 MEANS过程的补充 前面介绍了运用MEANS过程计算描述性统计量的基本用法,这里 补充介绍一下MEANS过程的其他功能和用法。 9.3.1 统计量列表 下面列出了MEANS过程中各种描述性统计量和位置统计量的关键 字及计算方法。MEANS过程在处理数据时,数据可以是详细数据,即 每条数据代表一个个体或者观测,也可是分组数据,即每条数据代表一 组个体或者观测,这种分组数据在医学统计上非常常见。 描述性统计量如表9.2所示。 表9.2 常见描述性统计量 在表9.1中wi代表权重,当没有使用WEIGHT语句指定权重时时,系 统默认wi=1。 位置统计量如表9.3所示。 表9.3 常见位置统计量 9.3.2 选项WEIGHT=和WEIGHT语句 若计算描述性统计量(均值、标准差、标准误差、总和、权重和 等)时,需要使用权重变量,可以通过两种方法指定权重变量,一种是 在VAR语句中使用选项WEIGHT=,第二种是直接在PROC步中使用 WEIGHT语句来指定权重变量。如果既使用了WEIGHT语句,又在VAR 语句中使用了选项WEIGTH=,那么系统优先使用选项WEIGHT=中指定 的变量作为VAR语句中变量的权重变量。极差、极值和缺失值个数等统 计量不受权重变量影响。 需要注意的是,权重变量必须是数值型变量。 ·当权重变量的取值等于0时,系统会将该条观测计入非缺失值个数 N中。 ·当权重变量的取值小于0时,系统将自动将取值转换成0,并将该 条观测计入非缺失值个数N中。 ·当权重变量的取值为缺失时,系统在处理过程中将自动忽略该条 观测。 FREQ语句也可以用来指定观测的频数,如果FREQ语句指定的变量 的取值为非整数,系统只取其整数部分;当该变量的取值小于1或者缺 失时,系统在进行分析时会自动忽略该条观测。 注意 当MEANS过程或者UNIVARIATE过程中使用WEIGTH语 句时,系统将不会计算偏度系数和峰度系数,输出的偏度系数和峰度系 数都为缺失值。 9.3.3 输出SAS数据集 默认情况下,MEANS过程中计算的各种描述性统计量都是输出到 结果窗口的。但往往在实际应用中,计算描述性统计量只是进行数据分 析的第一步,在得到各种统计量后,需要对其做进一步的加工分析。在 这种情况下,将输出结果保存成SAS数据集是非常必要的。OUTPUT语 句使得用户可以自行选择需要存储成SAS数据集的统计量。使用方法如 下: OUTPUT OUT=输出数据集<统计量关键字1<变量列表1><=列名1> <统计量关键字2<变量列表2><=列名2>…>>/<AUTONAME>; 其中: ·选项OUT=指定了输出数据集的名称,统计量关键字指定需要输出 到数据集中的统计量关键字,变量列表指定需要计算描述性统计量并输 出到数据集中的变量的名称,等号右边的列名表示存储到数据集中时统 计量关键字的变量名称。OUTPUT语句中指定统计量关键字只影响输出 到数据集中的统计量,而PROC MEANS语句中的统计量关键字序列则 影响输出到结果窗口的统计量,两者不相互影响。 ·使用选项AUTONAME时,系统自动为存储到数据集中的统计量指 定变量名称,变量名称自动置为“分析变量_统计量关键字”。 例9.4:数据集sashelp.shoes中包含了某鞋类公司全球范围内各种产 品的销售和库存情况,共有7个变量,变量Region代表地区,Product代 表产品,Subsidiary代表分公司,Stores代表门店个数,Sales代表销售 额,Inventory代表库存所占资金,Returns代表退货。计算各地区、各种 产品的平均销售额、平均库存金额、总销售金额、总库存金额及标准差 等统计量,并保存到SAS数据集中。 示例代码如下: proc means data=sashelp.shoes mean median sum std; title"Output Decsriptive Statistics to SAS Dataset"; var sales inventory; class region product; output out=work.outstat mean(sales)=sales_mean sum(sales)=sales_sum mean(inventory)= invnt_mean sum(inventory)=invnt_sum; run; proc print data=work.outstat; run; 这里使用CLASS语句指定了两个分类变量,运行上面的程序后,结 果窗口输出两个输出列表,第一个是MEANS过程的输出结果,第二个 是PRINT过程的输出结果,数据来自MEANS过程中输出的SAS数据集 WORK.OUTSTAT。 如图9.27和图9.28所示是部分输出结果。 图9.27 例9.4中MEANS过程输出内容 MEANS过程的输出报表和输出到数据集work.outstat的数据有两点 不同: 1)在MEANS过程的输出结果(第1张报表)中,含有均值、中位 数、总和和标准差4个统计量;而SAS数据集中,只包含了并未包含统 计量中位数,并且Sales的均值统计量被命名为Sales_mean,Sales的总和 被命名为Sales_sum,Inventory的均值被命名为Invnt_mean,Inventory的 总和被命名为Invnt_sum。如果不需要将统计结果输出到结果窗口,可 以在PROC MEANS语句中使用选项NOPRINT。 2)MEANS过程的输出结果和输出的SAS数据集观测的条数也不一 样。与MEANS过程的输出结果相比较,SAS数据集中包含了所有分类 变量组合的统计结果。系统用_TYPE_变量表示分类变量组合的种类, _TYPE_=0表示不使用分类变量的情形,也就是所有观测的统计结果, _TYPE_=1或_TYPE_=2表示仅使用一个分类变量的情形,_TYPE_=3表 示同时使用两个分类变量得出的分类结果。 图9.28 9.3.4 例9.4中PRINT过程输出内容 WAYS语句和TYPES语句 在CLASS语句指定多个分类变量时,MEANS过程提供了WAYS语 句、TYPES语句和选项NWAY,便于用户选择需要计算和保存的分类变 量组合的统计结果。 WAYS语句的基本语法为: WAYS 数值1数值2 数值3 …; 数值的取值为0到分类变量个数之间的任一整数(包括0和分类变量 个数),当数值=0时,表示输出不含分类变量情形下的描述性统计量; 当数值=1时,表示输出所有只使用一个分类变量情形下的统计结果;当 数值=2时,表示输出所有包含两个分类变量情形下的描述性统计量。 WAYS语句中可以指定多个数值,以下是WAYS语句的简单的例子。 class varA varB varC; ways 1 2; 系统将会输出分别按varA、varB、varC分类的统计结果,及 varA*varB、varA*varC及varB*varC交叉组合分类后的统计结果。 TYPES语句的基本语法如下: TYPES 分类组合要求; 例如: class varA varB varC; types varA varB varC varA*varB varA*varC varB*varC; 上面的TYPES语句和“ways 12;”的作用是一样的。 注意 如果仅需要输出不使用分类变量的统计结果,可以使 用“WAYS 0;”或者“TYPES();”。使用TYPES语句来选择需要输出到 数据集中的分类组合的种类,比在数据集后面使用选项WHERE更加节 约时间和内存。 另外,在PROC MEANS语句中使用选项NWAY,可以使得输出数 据集中只包含使用所有分类变量的情形。在例9.4中使用选项NWAY的 示例如下: proc means data=sashelp.shoesnoprintnway; title"Output Decsriptive Statistics to SAS Dataset"; var sales inventory; class region product; output out=work.outstat mean(sales)=sales_meansum(sales)=sales_sum mean(inventory)= invnt_mean sum(inventory)=invnt_sum; run; proc print data=work.outstat; run; 输出内容如图9.29所示。 图9.29 例9.4使用选项NWAY的输出内容 这里总结一下MEANS过程的各种语句。 PROC MEANS DATA=数据集<统计量关键字选项其他选项>; VAR 分析变量1 <分析变量2 …> ; CLASS 分类变量1 分类变量2 …; BY BY变量1BY变量2…; FREQ 变量n; WEIGHT 变量m; OUTPUT OUT=输出数据集<统计量关键字1<变量列表1><=列名1> …></AUTONAME>; TYPES分类组合要求; WAYS <数值1><数值2><数值3 …>; RUN; 在MEANS过程中,除了PROC MEANS语句和RUN语句,其他各个 语句的顺序可以互换,这个性质在SAS的其他PROC步中也适用。 SUMMARY过程和MEANS过程的语法和作用非常相似,和MEANS 过程不同的地方在于: ·SUMMARY过程在默认情况下不会将统计结果输出到结果窗口, 如果需要输出到结果窗口,则要在PROC SUMMARY语句中使用选项 PRINT。 ·在MEANS过程中,如果不使用VAR语句指定分析变量,系统会默 认输出所有数值型变量的描述性统计量。但是在SUMMARY过程中,如 果在PROC SUMMARY语句中指定了统计量关键字,却没有使用VAR语 句,系统将给出ERROR信息,并停止运行。 9.4 本章小结 作为第二篇统计分析部分的开篇,本章第1节介绍了统计学中一些 基本概念,如总体、个体和样本的定义,简单随机抽样的基本原理,参 数和统计量的区别和联系,自由度的定义,随机变量和概率分布的定 义,以及在统计学中具有重大作用的3种概率分布(二项分布、泊松分 布和正态分布)。第2节中,介绍了描述性统计分析在统计学中的重要 作用,分别讨论了描述数据集中趋势、数据离散程度及数据分布形态的 描述性统计量的定义及计算方法,并通过实例讲解如何运用SAS中的 MEANS过程和UNIVARIATE过程计算这些描述性统计量。 第10章 参数估计与假设检验 从本章起,开始介绍统计推断,这是统计学的一个重要组成部分。 所谓统计推断就是利用样本所提供的信息对总体的某些统计特征进行估 计或者判断,从而认识总体。统计推断分为两大类,一类是参数估计, 另一类是假设检验。本章中将介绍参数估计和假设检验涉及的基本概念 和基本原理,并结合SAS过程步介绍常见的单样本均值检验、两样本及 配对样本的均值检验、非参数检验的方法以及分布拟合的应用。 10.1 参数估计 参数估计和假设检验是统计推断的基本内容。在SAS中,几乎所有 统计建模的PROC步都会涉及参数估计及与之相应的假设检验。假设总 体ζ的分布函数的类型已知,但是其中的一个或者多个参数未知,那么 就需要对这些未知的参数做出合理的估计,并且对所做出的估计进行评 价,这样的过程就叫做参数估计。 例如,已知总体ζ服从正态分布,但其均值与方差未知。ζ1,ζ2, …,ζn是总体ζ的容量为n的样本,参数估计就是利用样本ζ1,ζ2,…,ζn 所提供的信息,对总体ζ的均值与方差进行估计的过程。参数估计的形 式有两种:点估计和区间估计。 10.1.1 点估计 点估计又称定值估计,简单地说,就是用一个单一的取值去近似地 作为未知参数的估计值。一般会根据样本ζ1,ζ2,…,ζn构建适当的统 计量 (ζ1,ζ2,…,ζn)作为未知参数θ的一个估计量,当样本的取值 为x1,x2,…,xn时,则以 (x1,x2,…,xn)作为总体分布中未知参 数θ的一个估计值。 样本的均值x可以用来估计总体的均值μ,样本的标准差s可以用来 估计总体的标准差σ。例如,在第9章中,已经分析了某个湖里种类为 Bream的鱼类的宽度样本数据,通过样本数据的计算得知,Bream的宽 度的样本均值为15.18,样本标准差为1.96。那么,这里的样本均值 15.18就可以作为整个湖中所有Bream鱼的宽度均值的一个估计。 在统计中,进行点估计的方法有多种,例如矩估计法、最小二乘 法、极大似然法,其中极大似然估计又有很多改进的形式,比如限制极 大似然等。SAS/STAT的PROC步中允许用户在进行参数估计的时候指 定不同的参数估计方法。 点估计的矩估计法是由皮尔逊(Pearson)提出的,它直观、简便, 是用样本矩作为总体相关矩的估计,特别是在不知道总体分布的情况 下,也可以对总体数学期望和方差进行估计。只要知道总体随机变量的 一些矩存在,就可以做相应的矩估计。但是,当总体的参数不能表示成 矩的函数时,就不能用矩估计;此外,矩估计常常没有利用总体分布函 数所提供的信息,因此也很难保证它有优良的性质。 极大似然估计的基本思想是,以使样本的出现获得最大概率的参数 值作为未知参数的估计值。例如,如果某事件发生的概率为p,且p只能 取0.01或0.9,现在,在连续两次实验中该事件都发生了,那么显然认为 p=0.9是合理的。又如,两人向同一目标各发射一枪,一人击中目标, 另一人没击中目标,认为击中目标者比没有击中目标者的射击技术好也 是合理的。极大似然法可以简单地理解为在所有可能的选择中选择“看 起来最像的”的值作为参数的估计,是参数估计用得最多的方法,最早 是由高斯在1821年提出的,但现在一般将其归功于R.A.Fisher,因为 Fisher在1922年再次提出了这种想法,并证明了它的一些性质,从而使 得极大似然法得到了广泛应用。 既然对于一个未知参数,可以提出不同的估计量,那么自然也就会 涉及比较估计量好坏的问题。这样一来,就需要给出评定估计量好坏的 标准了。无偏性、有效性和相合性是衡量一个参数估计好坏的3个基本 标准。 从理论上讲,当一个估计量的数学期望等于被估计参数的真实值 时,我们说,这个估计量是无偏的;简单地讲,一个无偏的估计量是指 当反复抽取样本的次数足够大时,由这些样本计算出来的该估计量的均 值可以无限接近被估计参数的真实值。无偏估计的实际意义就说无系统 误差。 假设比较参数θ的两个无偏估计为 1和 2,反复抽取样本足够多次 时,如果根据 1计算的取值较 2更加集中在被估计参数θ的真实值附 近,换句话说,也就是估计量 1比 2的方差更小,则 1比 2有效。 对估计量来说,除了要求它无偏、方差较小以外,还要求当样本容 量n增大时,它将越来越接近于被估计参数θ的真实值,这个要求是很自 然的,因为当n越大时,得到的关于总体的信息就越多。这个标准叫做 相合性,也叫做一致性。相合性是对一个估计量的基本要求,若估计量 不具有相合性,那么不论将样本容量n如何扩大,都不能将参数θ估计得 足够准确,这样的估计量是不可取的。在大样本场合下,极大似然法一 般都具备这三个性质。 10.1.2 区间估计 对于一个未知量,人们在测量和计算时,常不会满足于仅得到一个 近似值,还需估计误差,即要求知道近似值的精确程度。类似地,对于 未知参数θ来说,我们除了关心它的点估计 之外,还希望估计出一个范 围,并希望知道参数θ落在这个范围内的可信程度,这就是区间估计。 这样的范围通常以区间的形式给出,这样的区间即参数的置信区间。 1.置信区间 所谓置信区间,就是一个包含统计量值的取值范围,并且这个范围 在一定置信水平下包含参数θ的真实值。数学上的定义是,设某一个分 布F(x;θ),含有一个未知参数θ,对于给定值α,若由样本计算出的 两个统计量θ和θ(θ<θ),满足P{θ<θ<θ}≥1-α,则称随机区间(θ,θ) 是θ的置信水平为1-α的置信区间,1-α称为置信水平,α称为显著性水 平,θ和θ分别称为置信水平为1-α的双侧置信区间的置信下限和置信上 限。例如,图10.1是一个置信区间的例子,括号括起来的部分表示置信 区间,x是样本均值,μ是总体均值。 图10.1 置信区间示意图 置信水平为1-α的置信区间的含义是:对于一个确定的置信水平1α,若反复抽样足够多次,并且每次抽样的样本容量相等,每一个样本 都确定一个区间(θ,θ),这类区间要么包含θ的真值,要么不包含θ的 真值,如10.2图所示(从左边第二条竖线开始,每条竖线代表一个区 间)。那么根据大数定律,在这么多的区间中,包含θ的真值的区间约 占100(1-α)%,不包含θ的真值的区间约仅占100α%。 例如,在对总体的均值进行区间估计时,置信水平为95%的置信区 间(也称为95%置信区间)所表达的含义是,如果从总体中重复抽取 100个相同样本容量的样本,并且计算了出100个置信区间,则这100个 置信区间中有95个包含了总体的均值。 图10.2 不同样本下的置信区间 置信区间越短表示估计的精度越高,置信区间越宽表示估计的精度 越低。通常情况下,我们都希望能提高置信水平,但是在样本容量一定 的前提下,提高置信水平,势必会导致置信区间变宽。 2.均值的置信区间 前面在介绍样本均值x的计算方法时,也介绍了用来衡量样本变异 程度的统计量样本标准差s,这两个统计量都是用来刻画样本的。那么 如何刻画样本均值x的变异程度呢?这时需要引入一个新的统计量:均 值标准误差(Standard Error of The Mean)sx,sx的计算方法如下: 例如,Bream的宽度样本均值为15.18,样本标准差为1.96,均值标 准误差为 约为0.332。均值标准误差是刻画样本均值相对于总体 均值的变异程度的,该误差越小,估计越精确。从计算公式来看,我们 可以通过提高样本容量来提高估计的准确性。 对于服从正态分布N(μ,σ2)的随机变量X,若μ未知,则μ的1-α 置信区间可以通过以下方法求得: ·当σ2已知时,则μ的1-α置信区间为x-z1-1/2ασ≤μ≤x+z1-1/2ασ,当1-α为 0.95时,z1-1/2α=1.96。 ·当σ2未知时,则μ的1-α置信区间为(x-t·sx,x+t·sx),这里的t值是 根据t分布及α计算出来的,当样本容量足够大,1-α为0.95时,t值接近于 1.96。 注意 根据置信区间的计算公式,在同样的置信水平下,要提高 估计的精度,只能增加样本容量。增加了样本容量,则均值标准误差sx 将减小,从而置信区间的宽度也将变窄。 在计算均值的置信区间时,我们会假定X服从正态分布,但是通常 情况下,在分析一组数据时,是无从得知其是否服从正态分布的。事实 上,只要满足以下条件之一,就可以通过上述公式计算置信区间: ·总体服从正态分布。 ·满足中心极限定理的条件。中心极限定理指的是在样本容量足够 大的情况下,不论总体服从何种分布,样本均值都服从正态分布。相对 来讲,对于一个近似的对称分布,样本容量必须达到30及以上;若是一 个偏态分布,则对样本容量的要求更高。例如,Bream的样本容量为 35,偏度系数为0.2417,分布基本对称,满足中心极限定理的条件,也 就是说样本的均值服从正态分布。 图10.3描述了当样本容量增大时,样本均值的分布趋近于正态分 布。 图10.3 服从指数分布的数据分布直方图 从上面图10.3的总体数据中,有放回地随机抽取了1000个样本容量 为5的样本,样本均值分布则如图10.4所示。 图10.4 1000个样本容量为5的样本均值分布直方图 从上面图10.3的总体数据中,有放回地随机抽取了1000个样本容量 为10的样本,样本均值分布则如图10.5所示。 图10.5 1000个样本容量为10的样本均值分布直方图 从上面图10.3的总体数据中,有放回地随机抽取1000个样本容量为 30的样本,样本均值分布则如图10.6所示。 可以看出,虽然样本和总体的分布都是明显的偏态分布,但是当样 本容量达到30时,样本均值的分布基本已经服从正态分布。 图10.6 1000个样本容量为30的样本均值分布直方图 例10.1:计算Bream的平均宽度的95%置信区间。 示例代码如下: proc means data=sashelp.fish maxdec=2 n mean std stderr clm; where species="Bream"; var height; title"95% Confidence Interval for Bream"; run; 输出内容如图10.7所示。 图10.7 例10.1输出内容 统计量关键字STDERR指定计算标准误差均值,CLM指定计算均值 的置信区间。在本例中,我们认为区间(14.51,15.86)包含总体均值 的可能性有95%。并且,这里置信区间的宽度很窄,基本上,可以认为 样本均值是总体均值的一个比较准确的估计。 在MEANS过程中,可以使用选项ALPHA=来计算不同置信水平的 置信区间。ALPHA=0.01表示计算99%(也就是1-ALPHA)的置信区 间。默认情况下,ALPHA=0.05。 10.2 假设检验 在总体的分布函数只知其形式,但不知其参数的情况下,或者对总 体分布完全未知的情况下,为了推断总体的某些未知特征,先提出某些 关于总体的假设,然后要根据样本,采用适当的方法对所提出的假设作 出接受或者拒绝的决策,这一过程叫做假设检验。假设检验主要分为参 数假设检验和非参数假设检验。 10.2.1 基本原理 以下通过一个例子来介绍假设检验的基本原理和做法。 假设我们手里有一个一块钱的硬币,通常情况下我们都会认为硬币 的质地是均匀的。如果我们认为手里的这个硬币有可能是不均匀的,就 必须有足够的理由来证明。为了证明该硬币不均匀,我们制定了一个原 则,如果连续抛掷5次的结果都是正面朝上或者反面朝上,则认为该硬 币是不均匀的,否则,认为没有足够的证据证明该硬币是不均匀的。因 此,我们连续抛掷了5次硬币,并收集结果。有了抛掷的数据,就可通 过其正面朝上或者反面朝上的次数来判断该硬币是否均匀了。 在这里,该硬币是均匀的这一假设就是原假设,记为H0,如果没有 充分的证据,不能认为这枚硬币是不均匀的。和H0不相容的假设,也就 是硬币是不均匀的假设,称为备择假设,记为H1。在这个例子中,即使 硬币是均匀的,也有可能通过上述检验方法做出拒绝H0的判断,我们把 原假设H0为真时,拒绝原假设H0的所犯的错误,称为第一类错误或弃真 错误;另一方面,即使硬币是不均匀的,也有可能做出接受H0的判断, 我们把原假设H0为假时,接受原假设H0所犯的错误,称为第二类错误或 者取伪错误,如图10.8所示。犯第一类错误的概率记为α,犯第二类错 误的概率记为β,即 α=P{犯第一类错误}=P{拒绝H0|H0为真}, β=P{犯第二类错误}=P{接受H0|H0为假}。 图10.8 第一类错误和第二类错误 通常,称1-β为检验功效,即不犯第二类错误的概率,也就是正确 地拒绝原假设H0的概率。我们当然希望犯两类错误的概率都尽可能 小,但是当样本容量固定时,要使犯第一类错误和第二类错误的概率α 和β同时变小是不可能的,减小犯第一类错误的概率势必会增大犯第二 类错误的概率,反之亦然,此消彼长。因而,Neyman和Pearson提出一 个原则,即在控制犯第一类错误的概率α的条件下,尽量使得犯第二类 错误的概率β尽量小。这一原则的含义是,原假设要受到保护,不轻易 否定;若检验结果否定了原假设,则说明否定的理由是充分的。所以在 实际问题中,为了通过样本观测值对某一陈述取得强有力的支持,通常 把这种陈述本身作为备择假设,而将这种陈述的否定作为原假设。 在统计推断中,这种只控制α而不考虑β的假设检验,称为显著性检 验,α称为显著性水平。最常用的α的取值为0.05、0.01、0.001等。一般 情况下,根据研究的问题,如果犯弃真错误损失大,为减少这类错误, α的取值应适当地小,反之α的取值可以适当大一些。 假设检验的基本步骤可以分为如下4步: 1)根据实际问题和已知信息提出原假设和备择假设,如 H0:θ=θ0,H1:θ≠θ0, 或者,H0:θ≤θ0,H1:θ>θ0, 或者,H0:θ≥θ0,H1:θ<θ0, 或者,H0:F(x)=F0(x),H1:F(x)≠F0(x), 其中,θ为总体分布中的未知参数,θ0为参数空间中的一个已知 数。F(x)为总体的分布函数,F0(x)为某特定的分布函数。必须注 意,原假设H0一般是根据实际问题提出的,往往是从过去的经验和信息 中总结和提炼出来的,没有充分理由或者非常不利于原假设的观察结果 是不能拒绝它的。有时,通过检验不同的原假设,可能会得出完全相反 的结果。因此,提出什么样的原假设就比较重要了,应当更具以往的信 息和经验仔细考虑,提出适当的原假设。当根据样本数据拒绝了原假设 时,就说明原假设是显著不成立的。 2)给定的显著性水平(犯第一类错误的概率)α。 3)收集整理数据。 4)根据已知的样本数据,计算出P值,做出是否拒绝H0的判断,判 断的基本思想是小概率事件原理。 P值由已知样本计算出来的,表示在原假设H0为真的情况下观测到 该样本或比该样本更加极端的样本的概率。一个大的P值表示在原假设 H0为真的情况下,观测到该样本发生的概率很大;当P值很小时,表示 在原假设H0为真的情况下,观测到这个样本的概率很小,那么这个本来 应该是小概率的事件发生了,我们就有足够的证据怀疑这一假设的真实 性,从而拒绝原假设H0。这就是假设检验的基本原理。 例如,在前面扔硬币的例子当中,如果原假设为硬币是均匀的,如 果在100次试验当中,观测到了40次正面,60次反面,根据这个样本计 算出来的P值就表示在硬币是均匀的前提下,在100次这样的试验中观测 到40或小于40次正面的概率。 判断原则: 当P值≥α,接受原假设H0;当P值<α,拒绝原假设H0。 注意 在上面进行假设检验的过程中,显著性水平α是在收集整 理数据之前设定的,α的大小主要取决于用户可以接受的犯第一类错误 (当原假设为真时,拒绝原假设)的代价。如果犯第一类错误的代价极 大,则需要设定较小的α,如0.01。 上面已经讲了,在样本容量一定的情况下,α和β此消彼长,要同时 减小α和β就只能增加样本容量了。若β的值太大,则需要增加样本容量n 使得β变小。如果实际问题不需要β太小,则可以考虑适当减小n,以减 少人力物力。关于如何确定样本容量和β的关系,在本章中不展开讲, 有兴趣的读者可以参考SAS帮助文档中关于POWER过程的介绍。 10.2.2 T分布与T检验 T检验,也称为Student T检验,是运用T分布理论和假设检验原理进 行样本均数与总体均值的比较以及两样本均值的比较,由于T分布是在 正态分布总体的抽样中出现的,所以理论上要求样本来自正态分布总 体。T检验主要设计用于样本容量较小(例如n<30)、总体标准差σ未 知的正态分布样本的均值检验,但是实际在样本容量较大的情况下它也 同样适用。总的来讲,只要数样本满足正态性条件,就可以运用T检验 进行均值比较。满足以下两个条件之一,即满足正态性条件: ·样本来自正态分布总体。 ·样本容量足够大。如果样本是对称分布,样本容量达到30时即 可;如果样本不是对称分布,则样本容量应更大。 T检验分为单样本均值T检验和双样本均值T检验。 单样本均值T检验用于检验一个样本均值与一个已知的总体均值的 差异是否显著,统计量t为: 其中X为样本均值,μ0为总体均值,s为样本标准差, , 自由度为n-1。统计量t可以用来描述样本均值X和总体均值的差异。 当检验的原假设H0和备择假设H1分别为H0:μ=μ0,H1:μ≠μ0时, 如果我们要拒绝原假设H0,则统计量t的取值必须远大于0或者远小于 0,如图10.9所示。可以看到,位于T分布曲线的两段尾巴处,对应的p 值(也就是两段尾巴处的累积概率密度,即p({|Pr|>t}))应该很小。 图10.9 T分布密度曲线与正态分布密度曲线示意图 双样本均值T检验用于检验两个样本各自所代表的总体的均值的差 异是否显著。双样本均值T检验分为两种情况,一种是独立双样本均值 T检验,一种是配对样本均值T检验。更详细的介绍将在后面展开。 例10.2:运用UNIVARIATE过程中的选项MU0=来检验Bream的平 均宽度是否等于14。 示例代码如下: proc univariate data=sashelp.fish mu0=14; where species="Bream"; var height; title "Testing whether the mean of Bream height = 14 "; run; 在上述程序中,选项MU0=指定了位置检验中原假设的总体均值或 者位置的参数值。 部分输出如图10.10所示。 其中,统计量t标记为“Student t”,p值标记为“Pr>|t|”,统计量t的取 值为3.562859,p值为0.0011。因此,在显著性水平为0.05时,p值 <0.05,可以拒绝原假设,认为该湖中Bream的平均宽度和14具有显著差 异。符号检验(Sign)和符号秩检验(Signed Rank)都是非参数检验方 法。在利用T分布理论进行均值假设检验时,需要样本满足正态性条 件;当样本不能满足正态性条件时,可以运用符号检验和符号秩检验, 计算p值,再做出是否拒绝原假设的决定。 图10.10 例10.2输出内容 10.2.3 TTEST过程 TTEST过程可以进行单样本、独立双样本、配对样本均值T检验和 置信区间的计算,并且可以绘制直方图、分位数-分位数图(Q-Q图)、 盒状图和置信区间图。TTEST过程的基本语法如下: PROC TTEST DATA=数据集; CLASS两样本检验的分类变量; PAIRED配对样本检验的变量; VAR分析的变量; RUN; 其中: ·CLASS语句只能指定一个分类变量,并且该分类变量只能有两个 不同的取值,如果TTEST过程没有使用CLASS语句,则系统将进行单样 本均值T检验。当TTEST过程中使用了CLASS语句时,系统将进行独立 双样本均值T检验。 ·PAIRED语句指定在进行配对样本均值T检验时的配对变量组,一 个PAIRED语句中可以指定一个或者多个配对变量组。 ·VAR语句指定需要分析的数值型变量。 TTEST过程常会输出置信区间图与Q-Q图,这里先对此简单介绍一 下。置信区间图是将样本的统计量(如均值)和置信区间通过图形方式 展现出来,如图10.11所示。 图10.11 置信区间图 如果原假设指定的值(空值,也称为零值)包含在(1-α)%置信区 间中,则在显著性水平α下不能拒绝原假设;如果原假设指定的值不包 含在(1-α)%置信区间中,则在显著性水平α下应拒绝原假设。 Q-Q图和概率图类似,是一种以指定分布的分位数为横坐标、样本 值为纵坐标的散点图,如图10.12所示。Q-Q图运用实际数据的分位数与 所指定分布的分位数之间的关系曲线来检验数据是否服从指定分布,若 图中的散点都近似地在一条直线附近,则认为数据近似服从指定分布。 在TTEST过程中,Q-Q图用来检验数据是否近似服从正态分布。 图10.12 10.2.4 Q-Q图 单样本均值T检验 如前所述,单样本均值T检验是用于检验一个样本均值与一个已知 的总体均值的差异是否显著的。下面来看看示例。 例10.3:运用TTEST过程检验Bream的平均宽度是否等于14。 示例代码如下: proc ttest data=sashelp.fish h0=14 plots(shownull)=interval; where species="Bream"; var height; title "Testing whether the mean of Bream height = 14 " "Using PROC TTEST"; run; 在上述程序中,选项H0=指定了位置检验中原假设的总体均值或者 位置参数值,默认情况下,H0=0。选项PLOTS(SHOWNULL) =INTERVAL指定作置信区间图,SHOWNULL指定在置信区间图中根据 原假设的值作一条竖直的参考线。 输出内容如图10.13所示。 图10.13 TTEST过程描述统计量报表 其中,输出的第一个报表是描述性统计量,和UNIVARIATE过程中 输出的值一样。第二个报表是样本均值、95%置信区间,注意到95%置 信区间未包含原假设的取值14,这说明了在显著性水平α=0.05下,有充 分的证据证明Bream的宽度不等于14。 P值=0.0011<0.05也证明了这一点(如图10.14所示),应拒绝原假 设。TTEST过程计算得出的P值和UNIVARIATE过程中的一样。 图10.14 TTEST过程T值报表 接下来是默认输出的直方图和Q-Q图,如图10.15和图10.16所示。 因为在PROC TTEST语句中制定了选项PLOTS,所以同时也输出了置信 区间图,如图10.17所示。 图10.15 例10.3直方图 图10.16 例10.3Q-Q图 在图10.16所示的Q-Q图中,数据都近似地分布在直线附近,可以近 似地认为数据服从正态分布,也就是说,上述T检验的前提条件(正态 性条件)是满足的。 图10.17 10.2.5 例10.3置信区间图 独立双样本均值T检验 在双样本条件下,也可以通过构造t统计量利用T分布理论来比较两 样本所代表的总体的均值是否有显著差异,如果两个样本是完全独立 的,该检验就叫做独立双样本均值T检验,如图10.18所示。 进行独立双样本均值T检验时,样本必须满足以下3个条件: ·两个样本中的观测之间是相互独立的。这一条件通常是在试验设 计阶段考虑完成的。 ·两个样本必须分别来自正态分布总体,当样本量足够大时,可以 认为该条件满足。最简单的,可以通过作散点图、直方图或者Q-Q图来 观察样本是否服从正态分布。 ·两个样本方差相等。两个样本方差相等的检验,也称为两样本方 差齐性检验,可以通过构造F统计量及F统计量的分布理论进行检验。这 种检验称为F检验。F检验的原假设H0和备择假设H1如图10.19所示。 统计量 当原假设H0为真时,F的取值趋近于1,相应的P值也 应比较大(p>0.05)。 图10.18 独立双样本均值T检验的假设条件示意图 进行F检验时,数据必须服从正态分布,即使样本量很大时,也要 求数据服从正态分布。这个条件相对比较苛刻,当数据不服从正态分 布,我们只能通过作图来大致判断两样本的方差是否近似相等。当方差 齐性条件不满足时,可以使用近似T检验(结果也在TTEST过程中自动 输出)。 图10.19 方差齐性检验示意图 在进行T检验时,必须逐条检查上述3个假设条件是否满足,当不满 足以上假设条件时,使用T检验或者近似T检验可能导致不准确的结 论。 使用T检验进行双样本均值比较时,原假设H0和备择假设H1分别如 下: H0:μ1-μ2=0 H1:μ1-μ2≠0 例10.4:某次考试后,教导主任欲分析在该次考试中男生与女生的 学习成绩是否存在显著性差异,因此在该学校参加考试的1000名学生 中,随机抽取了100名同学作为样本,并保存在数据集ex.score中,数据 集中包含ID、Score和Gender三个变量,其中部分数据如图10.20所示。 图10.20 数据集ex.score中部分数据 下面运用T检验比较ex.score中男生和女生学习成绩是否有差异。 proc ttest data=ex.score plots(shownull)=interval; class gender; var score; title "Two Sample t test for Boys and Girls"; run; 在运用TTEST过程进行检验时,首先要检查数据是否满足T检验的 正态性条件。 TTEST过程中默认输出的直方图和Q-Q图可以快速检查数据是否满 足正态性条件,如图10.21和图10.22所示。 图10.21 例10.4直方图 可以看到,Q-Q图显示数据点都集中在直线附近,可以判断两个组 中数据都服从近似正态分布。如果从Q-Q图中看出数据明显不服从正态 分布,且样本容量不大时,可以使用非参数检验方法比较两组数据的均 值是否存在显著差异,SAS中的NPAR1WAY过程可以进行该项非参数 检验(将在10.3节中介绍)。 图10.22 例10.4Q-Q图 TTEST过程输出的统计报表如图10.23所示。 第1张报表中展示了两组样本的描述性统计量及两组样本均值之差 的标准差和标准误差。 首先来看一下第4张报表,第4张报表输出的是方差齐性检验的结 果,如图10.24中的第3张表所示。由于前面已经得知,两个样本都近似 服从正态分布,因此可以使用F检验进行两样本方差齐性检验,F值 =1.05,趋近于1,并且对应的P值=0.8920>0.05,因此不能拒绝原假设, 也就说在显著性水平α=0.05的情况下,不能拒绝两样本方差相等的原假 设。 图10.23 图10.24 例10.4描述统计量报表 例10.4TTEST过程部分报表 然后再来看第2张和第3张报表中T检验的结果(如图10.24中的第1 张和第2张表所示)。在TTEST过程中,系统自动输出了方差齐性满足 和不满足两种情况下T检验的结果(后者也称为近似T检验)。当方差 齐性条件满足时,应查看汇总方法(Pooled)的T值与P值;当方差齐性 条件不满足时,应查看Satterthwaite方法的T值与P值。这里我们已经知 道方差齐性条件满足了,且汇总方法对应的T值=1.92,P值 =0.0582>0.05,所以没有足够的证据证明男生和女生的考试成绩存在显 著差异。同时,在第2张报表中,均值之差的95%置信区间为 (-0.1422,8.1422),包含了0,暗含在95%置信水平下男生和女生考试 成绩的差别不大。 最后,来看两组均值之差的置信区间图,如图10.25所示。 图10.25 例10.4置信区间图 因为两组的方差非常接近,所以T检验(汇总)和近似T检验 (Satterthwaite)的置信区间及P值都非常接近,并且置信区间都包含了 0,和第2张报表中的输出一致。 1.根据统计数据进行T检验 有些时候,在医学临床试验中,我们并不能直接接触样本数据,只 能获取一些样本数据的统计结果,如样本容量、均值、标准差等统计 量,这时通过TTEST过程也可以进行两样本均值的比较。 例10.5:通过数据集ex.score计算出样本容量、均值、标准差、最大 值、最小值等统计量,并保存在数据集work.summary中,然后再运用 TTEST过程分析work.summary。注意查看结果是否与例10.4相同。 示例代码如下: proc sort data=ex.score; by gender; run; proc means data=ex.scorenoprint; var score; by gender; output out=work.summary; run; proc print data=work.summary; title "Work.Summary"; run; 查看Work.Summary中的数据,如图10.26所示。其中包含了5个变 量,Gender表示分组变量,_TYPE和_FREQ_是系统默认输出变量, _STAT_表示统计量名称,有5个不同的取值,分别为N、MIN、MAX、 MEAN、STD,Score是原数据集中的分析变量,这里保存了各个统计量 的值。 图10.26 数据集work.summary内容 示例代码如下: proc ttest data=work.summary; class gender; var score; title "Two Sample t test for Boys and Girls Using Summary Statistics"; run; 输出如图10.27所示。 图10.27 例10.5TTEST过程报表 可以看出,这里TTEST过程中除了没有输出默认的直方图和Q-Q图 (因为直方图和Q-Q图需要有原始观测值才能画出)以外,其余的报表 和例10.4中一模一样。 在运用TTEST过程进行T检验时,当数据集中包含变量_TYPE_或者 _STAT_时,系统默认输入的数据集是一个统计结果,而非原始数据。 在此种情况下,_STAT_的取值必须包含统计量N、MEAN和STD。如果 数据集中不包含这3个统计量,系统将报错。 注意 虽然使用统计结果也可以进行均值T检验,但是仅从统计 结果中,我们无从得知数据的分布情况,在输出T检验的结果时,也没 有办法检查数据是否服从检验的假设条件。当汇总方法和Satterthwaite 方法的P值差别比较大时,就无法分辨该依据哪种方法的P值给出接受原 假设或者拒绝原假设的结论。 2.单边T检验 在独立双样本均值T检验中,我们不仅关注两个样本所代表的总体 的均值是否存在差异,更加关注某一样本所代表总体的均值是不是显著 比另一样本所代表总体的均值更高或者更低。例如,在某种新降血压药 品的研发过程中,公司更关注的是该种药品是否真的能起到降血压作 用,某厂引进一种新方法生产固体燃料推进器,该厂更关注在新方法下 生产的推进器的燃烧率是否较以往生产的有显著的提高等。这种类型的 均值假设检验称为单边T检验。单边检验不仅可以在双样本的情况下运 用,在单样本的情况下同样适用。 单边T检验的原假设H0和备择假设H1分别为: H0:μ≤k,H1:μ>k 或者,H0:μ≥k,H1:μ<k 在TTEST过程中,选项SIDES=U的原假设H0和备择假设H1分别为 H0:μ≤k,H1:μ>k 选项SIDES=L的原假设H0和备择假设H1分别为 H0:μ≥k,H1:μ<k 单边T检验除了在双样本情况下适用,在单样本情况下也适用。 在上面分析学生考试成绩的例子中,根据以往的考试结果和升学 率,该校教导主任一直认为男生考试成绩普遍比女生考试成绩好,但是 最近他似乎觉得情况开始发生了转变,他偏向于认为现在女生的考试成 绩优于男生的考试成绩,因此他希望通过分析这次的考试成绩来证明他 的观点。那么,他分析的备择假设H1应该是:女生的平均成绩>男生的 平均成绩,也就是μ1-μ2>0,那么原假设H0应为:μ1-μ2≤0。 例10.6:分析数据集ex.score中女生的平均成绩是否比男生的平均成 绩显著更高(α=0.05)。 示例代码如下: proc ttest data=ex.score plots(shownull)=interval h0=0 sides=U; class gender; var score; title "One-Sided t test for Boys and Girls"; run; 程序中选项H0=0是系统默认的,用户可以通过选项H0=为原假设指 定任何值,选项SIDES=U表示原假设H0为μ1-μ2≤0,因为按字母排 Female排在Male前面,所以原假设如下: Female的考试成绩均值-Male的考试成绩均值≤0 输出如图10.28所示。 图10.28 例10.6TTEST过程报表 可以注意到,差值(Female-Male)均值仍然和例10.4中一样,但是 95%的置信区间已经不一样了,现在95%的置信区间的置信上限为正无 穷。T值为1.92,和例10.4中的t值一样,因为不管是双边检验还是单边 检验,是用同样的样本构造出的统计量t。当SIDES=U时,P值=P{Pr>t 值},取值为0.0291<0.05,说明在0.05显著性水平上,应该拒绝原假 设,也就是说,女生考试的平均成绩不比男生低。 10.2.6 配对样本均值T检验 在双样本均值T检验中,如果两组数据不是相互独立的,比如说是 相同样本在不同条件下所获得的数据,这种情况下,对两组数据的均值 差异进行的假设检验叫做配对样本均值T检验。例如,某学校在学期初 和学期末分别进行了两次推理能力测验,两次成绩分别记录在册,欲分 析这两次测验成绩是否具有显著差异。再例如,分析某种降血压药品的 使用效果时,现有的数据是某组病人在使用该种降血压药品前后的血压 值。对于这两种情况,都不可以使用独立双样本均值T检验,因为两样 本明显不符合相互独立的条件。这种情况下,要对两样本的均值差异进 行显著性检验,应该使用配对样本均值T检验的方法。 需要注意的是,在进行配对样本的T检验时,输入数据集不能是统 计结果,而必须是一条一条的观测值。并且样本必须同时满足以下两个 条件: ·两样本具有配对关系。 ·两样本的配对均值之差必须服从正态分布。当样本容量足够大 时,可以认为该条件满足。 配对样本均值T检验的原假设H0和备择假设H1分别为: H0:μpost=μpre,H1:μpost≠μpre 例10.7:数据集work.pressure中包含了12名男性在接受某种刺激前 后的心脏收缩压水平,欲分析这种刺激方法对心脏收缩压的影响。变量 SBPbefore中记录了在接受刺激前的心脏收缩压水平,变量SBPafter记录 了在接受刺激后的心脏收缩压水平。 示例代码如下: data work.pressure; input SBPbefore SBPafter @@; datalines; 120 128 124 131 130 131 118 127 140 132 128 125 140 141 135 137 126 118 130 132 126 129 127 135 ; run; proc ttest data=work.pressure; paired SBPbefore*SBPafter; title "Testing the difference before and after stimulus"; run; TTEST过程中PAIRED语句指定了进行配对分析的变量SBPbefore和 SBPafter。在计算配对的差值时是“*”号左边的变量减去“*”号右边的变 量。 首先输出数据的分布情况,如图10.29所示。 图10.29 例10.7直方图 在图10.29中,包括了配对差值的直方图、以配对差值的均值和标 准差为均值和标准差的正态分布图、核分布图、95%置信区间图,表示 差值(SBPbefore-SBPafter)基本服从正态分布。 图10.30 例10.7TTEST过程数据配对概况 在图10.30中,左图展示了数据的配对情况,每条细连线的左端是 每个参与者接受刺激前的收缩压值,右端是接受刺激后的收缩压值,粗 连线的左端是所有参与者接受刺激前的收缩压的均值,右端是接受刺激 后的收缩压平均值。右图中横坐标代表刺激前的收缩压,纵坐标代表刺 激后的收缩压,大部分参与者在接受刺激后血压上升了,只有3个点落 在对角线下方,表示仅有3个参与者在接受刺激后血压下降了,而且下 降的幅度也比较大,这使得差值的均值仍落在对角线附近,如图10.31 所示。 图10.31 例10.7Q-Q图 Q-Q图也表示了配对差值基本服从正态分布,如果要进行更加严格 的正态性检验,可以使用UNIVARIATE过程,并运用选项NORMAL。 配对样本T检验的结果如图10.32所示。 图10.32 例10.7TTEST过程报表 差值(SBPbefore-SBPafter)的T值为-1.09,自由度为11,对应的P 值为0.2992>0.05,所以T检验显示在显著性水平α=0.05下,该种刺激没 有显著影响心脏收缩压。 10.3 非参数假设检验 在上面的均值T检验中,我们分析的都是连续型变量,并且前提条 件是样本满足正态性条件。当分析变量不再是连续型变量,而是定序变 量或者正态性条件不能满足时,则应当使用非参数方法对均值和方差等 统计量进行假设检验。在前面10.2.2节中,运用UNIVARIATE过程对位 置参数进行假设检验时,系统除了给出了T检验的T值和P值,也提供了 两种非参数检验方法,即符号检验(Sign)和符号秩检验(Signed Rank)的统计量值和P值。 非参数检验的方法有秩和检验法和中位数评分检验法。秩和检验法 的主要思想是将原始数据转化成秩,利用秩构造统计量来比较不同样本 的分布。在这里,每个数据值的秩指的是,原数据按从小到大的顺序排 列后,该数据值在原数据中所处的位置。例如,一个样本中的数据值分 别为2,5,…,15,相应的秩则如图10.33所示。 这组数据被分成了两个组,A组的秩和=19(1+2+4+5+7),B组的 秩和=36(3+6+8+9+10),每组的秩和将被用来检验两组数据是否是等 同的。 中位数评分检验法的主要思想是将原始数据转化为中位数评分,利 用中位数评分构造统计量比较不同样本的分布。当计算中位数评分时, 如果数据值小于等于该组数据的中位数,中位数评分为0;如果数据值 大于该组数据的中位数,中位数评分为1。例如,两组数据如图10.34所 示。 图10.33 图10.34 数据和秩 数据和中位数评分 A组的中位数评分和=1,B组的中位数评分和=4,每组的中位数评 分和将被用来检验两组数据是否是等同的。 非参数检验的原假设H0和备择假设H1分别为: H0:所有组关于位置、形状和范围是等同的, H1:所有组关于位置、形状和范围不是等同的。 SAS中用于非参数检验的过程步为NPAR1WAY过程,NPAR1WAY 过程的基本语法如下: PROC NPAR1WAY DATA=数据集; CLASS用于做比较检验的分类变量; VAR 分析的变量; RUN; 其中: ·CLASS语句只能指定一个分类变量,并且该分类变量可以有任意 多个不同的取值。 ·VAR语句指定需要分析的数值型变量。 ·当样本量很小,或者厚尾,或者偏度很大时可以考虑用EXACT语 句;该语句会消耗大量系统资源和时间,样本量很大时近似检验也会给 出不错的结果,因此,不建议使用EXACT语句。 例10.8:数据集ex.Service中保存了某家银行分行下的3个支行里各 个柜台接待人员的服务水平数据。服务水平分为5类:非常好=5,较好 =4,一般=3,较差=2,非常差=1。现在要分析这3个支行的服务水平是 否存在根本性差异。ex.Service中包含3个变量,Store表示支行,ID表示 各柜员的ID号码,Servicelevel表示服务水平评分,部分数据如图10.35 所示。 示例代码如下: proc npar1way data=ex.service wilcoxon median ; class store; var servicelevel; title "Using NPAR1WAY to Compare Service Level"; run; 这里原假设指的是3个支行的服务水平的分布是等同的。选项 WILCOXON指定使用秩和检验,当分类变量Store有两个取值时,将输 出Wilcoxon两样本检验结果,当Store的取值多于两个时,将输出 Kruskal-Wallis检验结果;选项MEDIAN指定使用中位数评分检验。 输出内容如图10.36所示,盒状分布图如图10.37所示。 图10.35 数据集ex.Service部分数据 图10.36 例10.8秩和检验报表 图10.37 例10.8秩和检验盒状分布图 秩和检验的第一张报表中输出了3个支行的观测数、实际的秩和及 原假设H0为真时的期望秩和。从Kruskal-Wallis检验的结果来看,P值 =0.0246<0.05,因此,有足够证据拒绝原假设,也就是说,在显著性水 平α=0.05下,3个支行的服务水平显著存在差异。 中位数评分检验的报表和秩和检验的报表类似,如图10.38所示。 不同的是,秩和检验中用秩代替了原数据值,中位数检验中用中位数评 分代替了原数据值,P值=0.0259<0.05,表示了在显著性水平α=0.05下, 我们应拒绝原假设,3个支行服务水平显著存在差异。最后还输出各组 中位数评分的Mosaic图,如图10.39所示。 图10.38 例10.8中位数评分检验报表 图10.39 中位数评分Mosaic图 SAS里面主要的非参数检验方法除了秩和检验法和中位数评分检验 法之外,还有Van der Waerden、Savage、Siegel-Tukey、AnsariBradley、Klotz和Mood scores等方法。此外,在PROC NPAR1WAY语句 中使用选项EDF,系统还会通过计算经验函数统计量,来检验同一变量 在不同分组的情况下分布是否相同,检验方法包括Kolmogorov-Smirnov 检验和Cramer-von Mises检验。当只比较两个分类的分布时,系统还会 提供精确的Kolmogorov-Smirnov检验,有兴趣的读者请参考SAS帮助文 档深入学习。 10.4 分布拟合假设检验 对数据的分布进行正态性检验在实际中是十分有必要的,因为许多 统计资料的分析方法(如T检验和F检验)都要求数据分布是正态分布或 者近似正态分布。前面,已经介绍了通过计算统计量(如峰度系数和偏 度系数)判断数据是否近似服从正态分布,然后还学习了通过作图法, 如绘制Q-Q图和正态概率图来判断数据是否服从正态分布。由于我们已 经学习了假设检验的基本原理,这里将介绍第三种判断数据是否服从正 态分布的方法,即使用UNIVARIATE过程对数据进行正态分布的拟合优 度检验,它会更加严格地判断数据是否服从正态分布。在对样本进行正 态性检验的时候,UNIVARIATE过程中集成了以上3种方法。 运用UNIVARIATE过程对数据是否服从正态分布进行检验时,假设 检验的原假设H0和备择假设H1分别为: H0:数据服从正态分布, H1:数据不服从正态分布。 例10.9:检验数据集sashelp.heart中变量Systolic是否服从正态分布。 该数据集中包含了5209条观测。 示例代码如下: proc univariate data=sashelp.heart normal plot; var Systolic; histogram Systolic; run; 选项NORMAL指定UNIVARIATE过程对分布做正态性检验,如图 10.40所示。对于输出中的常规描述性统计量和直方图将不再多加解 释。 图10.40 例10.9正态性检验报表 图10.40所示的表是进行正态性检验的结果,SAS提供了3种检验方 法,这里不论哪一种检验方法计算得出的P值都小于0.05,所以应该拒 绝原假设,也就是说,在显著性水平α=0.05下,变量Systolic的分布显著 不服从正态分布。这里由于样本数较多,系统只输出了3种检验方法的 统计量和P值。当样本数小于2000时,系统同时还会输出Shapiro-Wilk检 验的统计量W和P值。 注意 SAS规定,在进行正态性检验时,当样本数n≤2000时,结 果以Shapiro-Wilk(也称W检验)为准;当样本数n>2000时,结果以 Kolmogorow-Smimov(也称D检验)为准。 UNIVARIATE过程内置的分布有Beta、Exponential、Gamma、 Normal、Lognormal、Weibull 6种。其中,Exponential分布(指数分 布)常用来描述“寿命”类随机变量的分布,例如家电使用寿命、动植物 寿命、电话的通话时间等,在排队论和可靠性中有着广泛而重要的应 用。Lognormal分布(对数正态分布)是对数为正态分布的任意随机变 量的概率分布。如果一个变量可以看作是许多很小独立因子的乘积,则 这个变量可以看作是对数正态分布,例如一个股票投资的长期收益率可 以看作是每天收益率的乘积,通过研究发现,该种长期收益率一般服从 对数正态分布。Weibull分布(韦伯分布)是可靠性分析和寿命检验的 理论基础,应用在很多领域,常被用来描述风速的分布。在无线通信技 术中,相对于指数衰减模型,Weibull分布对衰减具有更好的拟合度。 例10.10:探索下列数据集中的变量GAP符合哪种分布。 示例代码如下: data work.Plates; label Gap = 'Plate Gap in cm'; input Gap @@; datalines; 0.746 0.357 0.376 0.327 0.485 1.741 0.252 0.512 0.534 1.656 0.742 0.378 0.541 0.805 0.682 0.418 0.506 0.501 0.519 1.302 0.275 0.601 0.388 0.450 1.547 0.690 0.676 0.314 0.736 0.643 ; run; proc univariate data=work.Plates normal; var Gap; histogram /lognormal weibull gamma; run; 0.241 0.714 0.247 0.845 0.483 0.777 1.121 0.922 0.319 0.352 0.768 0.597 0.880 0.486 0.636 0.409 0.231 0.344 0.529 1.080 本例中分别对变量GAP进行了正态性检验,并拟合了对数正态分 布、Weilbull分布和Gamma分布,同时还对拟合的分布做出了假设检 验。 除了一般的描述性统计量的输出,正态性检验的输出如图10.41所 示。 图10.41 例10.10正态性检验报表 这里的原假设是该数据服从正态分布,系统输出了4种检验方法的P 值,由于样本量比较小,我们以Shapiro-Wilk检验的结果为准,该P值远 小于0.05,所以应该拒绝原假设,即该分布显著不服从正态分布。 图10.42输出了变量GAP的直方图和3种分布的拟合分布图。随后系 统分别给出了这3种分布的假设检验结果,如图10.43所示。 以上是拟合对数正态分布的参数估计输出和检验结果,尺度参数估 计为-0.584,形状参数为0.500,假设检验的P值大于0.05,即在显著性水 平α=0.05下,不能拒绝原假设。 同样的,图10.44是Weibull分布的参数估计和检验结果,尺度参数 Sigma的估计为0.719,形状参数C的估计为1.961,P值小于0.05,所以应 拒绝原假设,即在显著性水平α=0.05下,对数据服从Weibull分布的假设 是不合理的。 类似的,可以判断Gamma分布的假设结果P值大于的0.05,不能拒 绝Gamma分布的假设。 图10.42 例10.10直方图和拟合分布图 图10.43 例10.10中对数分布的假设检验 图10.44 例10.10中Weibull分布的假设检验 10.5 本章小结 统计推断就是由样本推断总体,它包括两个基本内容:参数估计和 假设检验。参数估计问题分为点估计和区间估计。点估计是适当地选择 一个统计量作为未知参数的估计。由于点估计不能反映估计的精度,于 是又引入了区间估计。在参数估计部分,本章还讲述了如何运用SAS的 MEANS过程得到参数的点估计和区间估计。 从本章第2节开始,讨论了假设检验问题。有关总体分布的未知参 数或未知分布形式的种种论断叫统计假设,人们根据样本所提供的信息 对所考虑的假设做出接受或者拒绝的决策,假设检验就是做出这一决策 的过程。一般地,人们总是对原假设H0做出接受或者拒绝的决策。但 是接受一个假设并不意味着确信它是真的,它只意味着决定采取某种策 略;拒绝一个假设也不意味着它是假的,这也仅仅是表示采取另一种不 同的策略。由于做出判断的依据是一个样本,并且样本是随机的,因此 不论是哪种情况,都存在选择错误的可能性。故而,在实际问题中,如 何选取原假设和备择假设需根据实际情况慎重判断。对此,本章介绍了 如何使用TTEST过程对单样本均值、两样本均值及配对样本均值进行T 检验;当样本不满足T检验的假设条件时,还介绍了如何使用 NPAR1WAY过程对均值进行假设检验。最后,讨论了如何使用 UNIVARIATE过程对数据的分布形态进行假设检验。 第11章 方差分析 在实际应用中,常常需要判断几组观察到的数据或者处理的结果是 否存在显著差异。比如,想要了解不同地区的信用卡用户在月均消费水 平上是否存在差异,就是多组数据是否存在差异的示例。至于不同处理 的结果是否存在差异的示例也有很多,例如,考察几种用于缓解手术后 疼痛的药品,它们之间的治疗效果即药效持续的平均时间是否存在差 异,实际上考察的就是不同的处理(将药品作用于患者)其结果是否存 在差异。 若上述的信用卡月均消费水平或治疗效果存在差异,那么这种差异 是统计显著的吗?也就是说,这种差异是某一个或几个因素作用的结果 吗?例如是由于地区差异或不同的药物引起的吗?还是纯粹随机误差 (如随机抽样过程)的体现呢? 本章介绍的方差分析(Analysis of Variance,ANOVA)就是用于检 验两组或者两组以上样本的均值是否具备显著性差异的一种数理统计方 法。 11.1 方差分析的基本原理 在方差分析中,我们把要考察其均值是否存在显著差异的指标变量 称为响应变量,对响应变量取值有影响的其他变量称为因素。例如,信 用卡消费水平和治疗效果为响应变量,地区和药品则为因素。在方差分 析中,因素的取值应为离散型的,其不同的取值称为水平。例如,每一 个具体地区或者每一种药品都对应着一个水平。根据因素的个数,方差 分析可以分为单因素方差分析和多因素方差分析。 11.1.1 方差分析的模型 为了更好地解释方差分析的模型,首先来看看单因素的情形。考虑 如下示例:现有4种用于缓解术后疼痛的药品1、2、3和4,为了研究它 们的治疗效果是否存在显著差异,对每一种药品都进行了4次试验。试 验结果如表11.1所示。 表11.1 试验结果 如果我们把每一种药品的治疗效果看成一个总体,本例要解决的问 题就可以归结为检验4个总体的均值是否相等的问题。记4种药品对应的 总体的均值为μi(i=1,2,3,4),那么,该检验问题的原假设H0和备 选假设H1分别如下: H0:μ1=μ2=μ3=μ4 H1:μ1,μ2,μ3,μ4不全相等 假设Yij为第j种药品的第i次试验(i,j=1,2,3,4)结果,例如 Y12=6,Y21=5。对于每一次固定的试验,药效的持续时间可以看成是由 该药品的平均持续时间和个体差异导致药效的持续时间差异这两部分组 成的,即 第j组药品的第i个疗效=药品j的疗效+服药个体间的差异 将上述式子以符号的形式表示如下: Yij=μ1+εij (1) εij表示第j个水平下第i个观测与该水平均值之间的差异,也称误差 项(Error Term)。在实际问题中,误差项表示除考虑因素之外的其他 因素或者其他不可观测的随机因素(如天气等)的影响。在方差分析 中,一般假定不同水平下的εij服从均值为0、相同方差的正态分布,即 εij~N(0,σ2),且彼此间相互独立。上述模型(1)也是单因素方差分 析的一般模型。事实上,为了突出水平间的作用,我们常常将模型 (1)改写为: Yij=μ+Yj+εij (2) 其中,μ表示因素的均值,τj表示该因素下第j个水平的效应(Effect of Treatment j),记该等式表示的模型为模型(2)。例如,在上述药 效的例子中,μ为所考察的4种药品作用于患者的药效平均持续时间,τj 为第j种药品和μ之间的差异,即该药品的效应。 从上面方差分析模型可以看出,方差分析本质上是一个线性问题, 对于该问题,理论上,我们可以利用最小二乘法对模型进行拟合,得出 具体μj值或τj,进而判定水平间是否具有显著差异。但是,在实际计算 中,一般不会直接对模型进行拟合,而是追溯响应变量变化的来源,即 其方差的来源,来判定均值间是否有显著性差异。 11.1.2 方差分析的基本思想 响应变量的方差既可以是因素不同水平间的差异,也可以是抽样过 程本身。前者可以由模型中的因素解释,后者则对应了模型中的误差项 部分。 在方差分析中,我们将所有样本响应变量的方差称为全部平方和 (Total Sum of Squares,SST),公式如下: 这里Yij是第j个水平Aj下的第i个观测, 是所有抽样的均值。在上 述药品试验的例子中, 为4种药品16次试验的结果均值。 我们将由因素不同水平间差异引起的、可以由模型中因素解释的部 分方差称为模型平方和(Model Sum of Squares,SSM);将由抽样过程 本身引起的部分方差称为误差平方和(Error Sum of Squares,SSE)。 二者计算公式分别如下: 其中,Yj为水平Aj下所有样本的平均值,nj为该水平下样本观测数 目。在上述例子中,Yj为第j种药品所进行的4次试验结果的平均值,nj 为4。公式中(Yj- )2描述的是水平Aj下抽样均值和所有抽样均值之间 的差异,nj可以看该水平对应的权重。 上述3个统计量SST、SSM和SSE三者的关系如下: SST=SSM+SSE 在响应变量方差中,如果由因素不同水平引起的差异占显著比例, 那么可以推断该因素对响应变量的差异具有显著作用;反之,如果抽样 过程本身引起的差异占显著比例,那么可以推断该因素对响应变量的差 异不具有显著作用。在方差分析中,衡量上述两部分比例大小的统计量 为F统计量,其计算方法具体如下: 在上述公式中,s是水平的个数,n为所有水平下的样本容量的总 和,(s-1)为模型的自由度,(n-s)为误差自由度的自由度。F比值中 的分子和分母分别称为模型均方(Mean Square Model,MSM)和误差 均方(Mean Square Error,MSE)。 此外,在多元统计分析中,我们称SSM和SST的比值为R方,符号为 R2,(也称决定系数,Coefficient of Determination),即 该统计量用于衡量模型能解释响应变量方差比例的大小,其取值介 于0与1之间,其值越大意味着模型能解释的比例越大,即模型对数据的 拟合得越好,当其值趋近于0时,模型几乎不能解释响应变量方差。 在实际计算中,方差分析的步骤如下: 1)建立原假设与备选假设。原假设为s个水平对应的均值相等,备 选假设为s个水平对应的均值不全相等。 2)给定显著性水平α,在SAS方差分析的过程步中,该值默认为 0.05。 3)根据计算 统计量的值F0。 4)根据模型的自由度(s-1)以及误差自由度的自由度(n-s),可 以确定一个F分布。由该F分布的概率密度函数和F0,可以进一步计算出 在该F分布中大于F0的p值,p=Pr(X>F0)。例如,如图11.1所示的是模 型自由度为3、误差自由度为12的F分布概率密度图,从图中可以看出对 应F0=4的p值为0.035(曲线下位于直线F0=4右方尾部部分的面积为 0.035)。 5)若p值小于给定的显著性水平α,那么可以拒绝原假设,认为s个 水平对应的均值不全相等;反之,则接受原假设,认为s个水平对应的 均值相等。 图11.1 11.1.3 F(3,12)概率密度图 方差分析的假设 回顾模型11.1.1节中的模型(2)中方差分析的假设条件为: εij~N(0,σ2),εij相互独立。残差本质上来源于抽样样本。因此,该 假设条件等价于: ·每组观测服从正态分布。 ·每组观测的方差相等,即方差齐性。 ·样本数据集中观测间是独立的。 上述3个假设前提条件中,观测间相互独立意味着样本数据集中某 一个观测所包含的信息与其他观测均无关。一般地,首先,在进行方差 分析试验的初始阶段就应该验证观测间是否独立;其次,在实际应用 中,往往并不要求观测严格服从正态分布,如果观测近似服从正态分布 (当观测数目足够多的时候,一般认为观测的分布是服从正态的),就 认为其满足方差分析的正态性假设;最后,方差齐性可以通过假设检验 来判断,有关这方面的内容,我们会结合SAS的过程步一并介绍。 11.2 单因素试验的方差分析 在方差分析中,最简单的情形为单因素,熟练掌握单因素的方差分 析对理解、解决多因素方差问题很有帮助。本节介绍如何使用SAS进行 单因素的方差分析。 11.2.1 TTEST过程、ANOVA过程与GLM过程的区别 在SAS中,方差分析可以通过PROC TTEST、PROC ANOVA与 PROC GLM实现。具体采用哪一个过程步,需要考虑如下情景: ·仅有一个因素且该因素仅包含两个水平。在这种情形下,采用上 述3个过程步得到的结果一致。 ·仅有一个因素,但该因素包含的水平个数为3个或者3个以上。此 时若使用PROC TTEST,则需要进行多次的两两比较,这会大大增加犯 第一类错误的概率。因此,在这种情形下,不宜使用PROC TTEST,可 以考虑采用PROC ANOVA和PROC GLM。 ·因素的个数为两个或者两个以上,可以使用PROC ANOVA或者 PROC GLM。二者的区别是PROC ANOVA是专门针对均衡数据 (Balanced Data)的试验而设计的。所谓的均衡数据指的是每一个因素 及水平的组合的样本容量大小一致。由于考虑了数据的均衡性,PROC ANOVA在处理均衡试验时一般会比PROC GLM更快、占用的存储空间 更小。此外,PROC GLM提供了更多的图像输出选项供用户使用。 PROC TTEST在上一章有所介绍,本章将不重复。 11.2.2 使用ANOVA过程进行方差分析 前面提到的药品疗效的例子就是一个单因素多水平的情形,且该例 子是一个均衡数据。根据前面的讨论得知,可以采用PROC ANOVA或 者PROC GLM来进行分析。这里介绍如何利用PROC ANOVA实现分 析。 PROC ANOVA的语法如下: PROC ANOVA<选项>; CLASS 变量; MODEL响应变量= 因素; BY 变量; MEANS因素; RUN; 其中: ·PROC ANOVA语句中常见的选项有DATA=、OUTSTAT=和 PLOTS。其中,选项DATA=指定输入数据集,默认值为最近一次使用 过的数据集;选项OUTSTAT=指定输出数据集,该输出数据集包含自由 度、F统计量等;选项PLOTS要求ODS图像选项是打开的。 ·CLASS语句需在MODEL语句前,在CLASS语句中,用户指定用于 方差分析模型的分类变量,常见的有性别、组别等。 ·在MODEL语句中,用户指定用于方差分析的因素与响应变量。 ·PROC ANOVA会对BY语句中的每一个变量做独立的分析,使用 BY语句的前提是输入数据集已经按照BY语句中的变量升序排列,如果 BY语句中的变量个数不止一个,将只有最后一个变量起作用。如果输 入数据集未按照BY语句中的变量升序排列,用户可以使用PROC SORT 对数据集进行预先处理。 ·MEANS语句计算与因素对应的响应变量的均值。一个PROC ANOVA可以包含多个MEANS语句,所有的MEANS语句的位置必须在 MODEL语句之后。 例11.2:数据集ex.ReliefTime中包含了表11.1中的信息。假设方差 分析的3个假设条件满足:即病人术后疼痛延缓时间相互独立,试验中4 组观测时间在分布上符合正态分布且方差相等。现在要使用PROC ANOVA分析数据集ex.ReliefTime进行分析。 示例代码如下: data ex.ReliefTime; input Medicine $ Hours @@; datalines; A 7 A 5 A 3 A 1 B 6 B 5 B 3 B 3 C 7 C 9 C 9 C 9 D 4 D 3 D 4 D 3 ; run; proc anova data = ex.ReliefTime; class Medicine; model Hours = Medicine; run; 这里的原假设为数据中4种药品在延缓手术疼痛时间上均值相等。 提交上述代码,得到输出结果,共两部分,第一部分是汇总信息,如图 11.2所示。 第二部分是方差分析部分,结果如图11.3所示。 在本例中,因素药品的水平共有4个,因此对应模型的自由度为3; 校正合计的自由度等于数据中观测数减1,因此,其自由度为15;误差 自由度为校正合计自由度与模型自由度之差。上述代码的显著性水平α 为默认值0.05,F检验具有显著性:p值为0.0029小于α值,因此拒绝原假 设。也就是说,我们认为上述4种药品在延缓术后疼痛的时间上是有显 著性差异的。 图11.2 例11.2ANOVA过程输出汇总信息 图11.3 例11.2方差分析报表 在ODS图形选项打开的情况下,那么PROC ANOVA还会自动生成 一个盒状图。例如,提交下面代码,除了生成和例11.2中一样的结果 外,还生成了一个盒状图。 ods graphics on; proc anova data = ex.ReliefTime; class Medicine; model Hours = Medicine; run; ods graphics off; 盒状图如图11.4所示。 图11.4 例11.2输出盒状图 从箱图上可以直观地看出:C组与其他组在延缓术后疼痛时间上有 明显差异。 11.2.3 使用GLM过程进行方差分析 在上一个例子中,我们假定方差分析的3个假设条件均满足。实际 中,是需要验证这3个条件是否满足的。重新考虑例11.2中的问题,假 设每个病人服药后延缓疼痛的时间相互独立是一个合理的假设,现在仅 需要验证其余两个条件是否满足。PROC GLM中的选项提供了验证其他 两个条件的工具。PROC GLM不仅可以用来做方差分析,还可以用来进 行多元回归分析、协方差分析、多项式回归等。但这里仅介绍和方差分 析相关的部分内容,其他功能在后续章节根据需要再介绍。值得一提的 是,PROC GLM中方差分析部分的语法和PROC ANOVA语法有很多相 同之处,对于相同的之处,将简略带过。 PROC GLM的一般形式如下: PROC GLM<选项>; CLASS <选项>; MODEL 响应变量=因素; LSMEANS; MEANS<选项>; RUN; 其中: ·PROC GLM中常见的选项有,选项ALPHA=指定显著性水平α的 值,其默认值为0.05;选项DATA=指定用来分析的数据,OUTSTAT= 指定输出数据集,PLOTS=用来控制输出的图形(前提是ODS图形选项 打开)。 ·CLASS用来指定分类变量信息。 ·MODEL语句指定分析的因素与响应变量。 ·LSMEANS语句计算指定变量的最小二乘均值(Least Squares Means)。用户可以指定一个或者多个变量,需要注意的是,这些变量 都必须在之前的MODEL语句中出现过,具体用法将在后面介绍。 ·MEANS语句用来计算因素及其对应的响应变量的算术平均数和方 差。MEANS语句中常见的选项为HOVTEST,该选项使用LEVENE检验 判断各组方差是否相等(这里原假设为方差相等),LEVENE检验是选 项HOVTEST的默认值。 需要指出的是,PROC GLM是一个支持交互性的过程步:PROC GLM会一直在后台运行,直至遇到下一个过程步、数据步或者QUIT语 句。 例11.3:使用PROC GLM对数据集ex.ReliefTime进行方差分析假设 的检验,并进行方差分析。 示例代码如下: ods graphics on; proc glm data = ex.ReliefTime plots(only) = diagnostics; class Medicine; model Hours = Medicine; means Medicine / hovtest; run; quit; ods graphics off; 上述代码中PROC GLM语句中的PLOTS选项生成了拟合诊断图,如 图11.5所示。借助与Q-Q图和残差图(分别位于下图的第1列的第2、3 行),我们可以判断数据的正态性是否满足。从Q-Q图上可以看出,多 数观测位于直线的两侧;此外,从残差图可以看出,数据有唯一的最高 峰、且基本呈对称分布。根据这两点,可以认为原始数据呈近似正态分 布。 图11.5 例11.3拟合诊断图 此外,代码中语句“means Medicine/hovtest;”用于检验方差是否相 等,其输出结果如图11.6所示(位于整个PROC GLM输出结果的末 尾)。 图11.6 例11.3方差齐性检验报表 由于p值0.0661大于默认值0.05,因此我们接受原假设,即认为方差 是相等的。至此,方差分析的其余两个假设条件验证完毕。整个模型的 输出结果如图11.7所示。 图11.7 例11.3方差分析报表 从模型中可以看出,p值为0.0029,小于0.05,因此,我们拒绝原假 设,即认为4种药品在延缓术后疼痛的时间上存在显著性差异。这与我 们在例11.2中使用PROC ANOVA分析的结果一致。不过,注意,这里 出现了两个类似的表格:Ⅰ型SS(Sum of Square,即平方和)与Ⅲ型 SS,这是因为计算平方和时采用的方法不一样,在单因素的情况下,二 者是相等的。除了Ⅰ型SS和Ⅲ型SS外,PROC GLM还定义了Ⅱ型SS和 Ⅳ型SS,默认情况下,PROC GLM仅输出Ⅰ型SS和Ⅲ型SS。在实际 中,我们一般以Ⅲ型SS为主要参考依据。有关4种不同SS的区别,可以 参考SAS帮助文档,这里就不展开讨论。 11.3 显著因素下的水平间差异检验 在前面已经介绍了如何判断显著因素。本节考虑在显著因素确定的 情况下,如何进行水平间差异性的分析。 11.3.1 LSMEANS语句与MEANS语句的区别 在11.2节中,介绍了PROC GLM中MEANS语句的使用方法,而 且,在介绍语法的时候还提及了LSMEANS语句。MEANS语句和 LSMEANS语句在对均值的计算方法上存在一定的差别。 假设有两种新药A和B,在两个不同的医院进行了临床试验,数据 收集如表11.2所示。 表11.2 临床试验数据 根据表11.2可知,药A在两所医院的均值为24/5=4.8,药B在两所医 院的均值为26/5=5.2。LSMEANS语句在进行计算时使用的方法有所不 同,具体如下:首先计算药A在医院甲的均值为3,其次计算药A在医院 乙的均值为7.5,最后二者取平均值得到LSMEANS为5.25。因此,可以 得到如表11.3所示的结果。 表11.3 MEANS语句与LMEANS语句计算的均值 从MEANS的角度看,药B比药A要好(假设值越大代表效果越 好),但是从LSMEANS的角度看,二者无差异。一般情况下,若数据 是均衡的,二者的计算结果就是相同;但如果数据不均衡,二者的结果 也会不一样。从使用的角度看,若试验是不均衡的,则应该使用 LSMEANS。此外,LSMEANS也用于某个因素下水平间差异的检验。 例如,假设在方差分析后,我们找出了某一个因素具有显著作用,现欲 在分析该因素的两两水平间的差异,这时候就可以用LSMEANS。 11.3.2 利用LSMEANS语句进行水平差异分析 语句LSMEANS的使用语法如下: LSMEANS 因素<选项>, 在方差分析中,常见的选项有两个:PDIFF与SLICE,前者用于水 平间的两两比较,后者用于多因素分析,当因素间有相互作用时,可固 定一个因素的水平,进而分析另外一个因素的变化。有关SLICE选项的 用法会在下一节双因素试验中介绍。需要指出的是,与MEANS语句类 似,一个PROC GLM中可以指定多个LSMEANS语句。 PDIFF的常见选项如表11.4所示。 表11.4 选项PDIFF和ADJUST取值 控制选项误差的方法大体上有两种:一种是控制两两比较的误差 (Comparisonwise Error Rate,CER),另一种是控制整个试验的误差 (Experimentwise Error Rate,EER)。如果选择的是前者(CER),那 么可以使用语句LSMEANS/PDIFF=ALL ADJUST=T;若是后者,则可 以使用语句LSMEANS/PDIFF=ALL ADJUST=TUKEY或者 LSMEANS/PDIFF=CONTROL(‘控制组’)ADJUST=DUNNETT。取值 TUKEY适用于仅考虑水平间两两比较的情形。 例11.4:在例11.3中,我们发现因素Medicine的确是一个显著因 素。该因素共有4个水平:A、B、C和D,现欲分析两两比较药的疗 效。 示例代码如下: ods graphics on; proc glm data = ex.ReliefTime ; class Medicine; model Hours = Medicine; lsmeans Medicine/pdiff = All ; run; 提交上述代码,在结果查看窗口中,首先可以看到每个组的 LSMEANS信息,如图11.8所示。 紧接着是LSMEANS部分主要的输出结果,如图11.9所示。一个4×4 阶矩阵,记为Mij,这里的i,j=1,2,…,4。下标值i,j对应的信息由 上一个表格中的列LSMEAN号确定。原假设为i和j的LSMEANS值相等 (无显著性差别),显著性水平仍然为默认值0.05。 图11.8 例11.4输出的LSMEANS信息 图11.9 例11.4变量MEDICINE水平间差异分析报表 由于M12=0.9960>0.05,所以我们接受原假设,认为二者间无显著 性差别。同理,由于M43=0.0040<0.05,拒绝原假设,认为二者有显著 性差别。类似地可以对矩阵中的其他元素值进行分析。 语句LSMEANS中的选项pdiff=All要求ODS选项是打开的,其作用 是生成如图11.10所示的置信区间图。 图11.10 例11.4不同水平间均值差异的置信区间图 在图11.10中,斜向上的对角线(虚线)是组间差异为0的参考线。 斜向下的实线代表了CLASS变量下两个不同水平间的均值。本例中, CLASS变量下共有4个不同水平,可以组成6对不同水平组合。每一对水 平组合下的两个水平之间的均值差异的置信区间由图中斜向下的实线表 示。例如,直线L对应的是水平D和水平C均值差异的置信区间(通过水 平D的垂直线与通过水平C的水平线这两条直线的交点位于L上)。直线 L与斜向上的虚线没有交点,即0不在该置信区间内。据此,我们可以推 断水平D和水平C的均值存在显著差异。简而言之,如果斜向下的实线 与参考线不相交,就表示对应于该实线的两个水平的均值是有显著差异 的;反之,若实线与参考线相交,则该实线对应的两水平均值没有显著 差异。 11.4 双因素试验的方差分析 在实际应用中,更多出现的是包含多因素的试验和处理。多因素试 验与双因素试验背后的基本思想是一致的。下面以双因素试验为例介绍 如何对其进行方差分析。 11.4.1 双因素试验概述 与单因素方差分析不同,在双因素方差分析中因素间可能会有交互 作用。假设有两个因素A和B,因素A和B没有交互作用指的是A的水平 值不取决于B的水平值,反之亦然。对于有交互作用的因素,我们不可 孤立地看待这些因素。对于双因素的情形,一般从图像上看,没有交互 作用的因素水平图表现为两条不相交的线段,而有交互作用的因素水平 图为两相交的线段。如图11.11所示是在研究年龄和性别对身高是否有 显著作用过程中,因素年龄与性别之间的交互作用。从图像上看,两曲 线没有明显相交,据此可以推测二者间不存在相互作用。当然,要判定 是否存在或者不存在交互作用,还需要根据相应的统计量来分析。 图11.11 因素年龄与性别交互图 如果因素A和B间不存在交互作用,那么可以对因素A和B各自进行 独立分析,在后续的分析中去除不显著的因素。如果方差分析的结果显 示因素A和B间存在交互作用,这时候要对数据进行进一步的分析,具 体包括如下几点: ·在因素A的某个水平下,因素B对响应变量的作用。 ·在因素B的某个水平下,因素A对响应变量的作用。 ·在所有因素(A,B)的组合中,哪两组的差异最大。 需要指出的是,多因素的情况与双因素处理方法类似,只不过分析 的因素会增多。表11.5从模型因素角度对比了双因素与三因素。表中 带“*”号的表示因素间的相互作用。 表11.5 11.4.2 双因素与三因素比较 利用GLM过程对不均衡数据进行方差分析 数据集sashelp.class包含了学生的姓名、性别与身高,如图11.12所 示。现在分析年龄与性别是否是影响体重的显著因素。该问题属于不均 衡数据集的方差分析,可以使用PROC GLM实现,具体见下例。 例11.5:假设上述数据集学生数据符合方差分析模型的假设条件。 在显著性水平为0.05的条件下,分析数据sashelp.class中影响体重的显著 因素,并对显著因素中的水平(或水平组合)差异性进行分析。代码如 下: proc means data = sashelp.class N mean ; class age sex; var height; run; 首先,通过一些描述性统计量(观测数目与均值)对数据有一个初 步的了解,如图11.13所示。 图11.12 数据集学生数据 图11.13 例11.5中描述性统计量报表 从上述结果中可以看出,这不是一个均衡的数据。因此,可采用 PROC GLM进行方差分析。代码如下: proc glm data = sashelp.class; class Age Sex; model Weight = Age Sex Age*Sex; run; 输出结果中模型部分的结果如图11.14所示。 在本模型中,不同的年龄与性别组合共有11对,因此模型的自由度 为11-1=10;数据集共有19条观测,故校正合计对应的自由度为191=18,误差的自由度为二者值差。第1个表格显示模型的F值为4.14,p 值为0.0278,小于默认阈值0.05,据此,可以初步判断因素对体重是有 显著作用的,至于是哪个因素还需要具体分析。 源Age*Sex的作用是判断二者间是否有交互作用。从图11.14中的最 后一个表格可以看出,源Age*Sex的p值为0.5487(大于显著性水平 0.05),据此可以认为因素Age和Sex没有相互作用。因此,可以独立考 虑上述两个因素。此外,根据最后一个表格中源Age和源Sex对应的p 值,可以判断出因素Age是体重的一个显著因素,而Sex不是。 本例中的显著水平只有一个Age。为了进一步分析Age的水平间差 异性,提交下面的代码: lsmeans Age/pdiff=all adjust = tukey; run; 输出结果如图11.15所示。 图11.14 例11.5方差分析报表 图11.15 变量AGE水平间差异分析报表 由于年龄在16岁的学生只有一个(只有男生),因此这一类无法与 其他组进行比较,从而导致方阵中出现缺失值。从该矩阵中可以判断出 水平间的两两差异。此外,Diff的置信区间图如图11.16所示。 图11.16 11.4.3 例11.5变量AGE 水平间均值差异的置信区间图 有交互作用因素的方差分析 在例11.5中,介绍了双因素分析,在该例中因素间并没有相互作 用。本节通过一个例子来解释在因素间相互作用的情况下如何进行方差 分析。 例11.6:数据集ex.fruit记录了在不同湿度和温度下某种植物的产 出。这是一个双因素方差分析的情形。假设方差分析的假设条件满足, 在显著性水平0.05的前提下,欲分析不同温度、不同湿度下产出是否有 显著差异,以及温度和湿度的交互是否显著差异,如果交互有差异,分 析在湿度一定的情况下,温度对产出的影响。 示例代码如下: data ex.fruit; input humidity datalines; A1 B1 58.2 A1 A1 B2 56.2 A1 A1 B3 65.3 A1 A2 B1 49.1 A2 A2 B2 54.1 A2 A2 B3 51.6 A2 A3 B1 60.1 A3 A3 B2 70.9 A3 A3 B3 39.2 A3 ; run; $ temperature $ output_lbs @@; B1 B2 B3 B1 B2 B3 B1 B2 B3 52.6 41.2 60 42.8 50.5 48.4 58.3 73.2 40.7 首先,进行交互性分析,代码如下: proc glm data = ex.fruit; class humidity temperature; model output_tons = humidity temperature humidity*temperature; run; 模型部分的输出结果如图11.17所示。 从结果中可以看出:因素humidity和因素temperature间存在交互作 用;因素humidity是一个显著因素;因素temperature不是一个显著因 素。 下面在humidity的每个水平下分析因素temperature的作用,代码如 下: lsmeans humidity*temperature/slice=humidity; run; 提交上述代码,首先输出的是每一组humidity*temperature的最小二 乘均值,如图11.18所示。 图11.17 例11.6关于交互性作用的方差分析报表 图11.18 例11.6中最小二乘均值报表 第2个表格展现的是在每个湿度的水平下温度对产出的显著性检 验,如图11.19所示。 图11.19 例11.6中不同湿度水平下温度对产出的显著性检验报表 从该表格可以看出,在湿度为A1和A3的情况下,温度对产出是有 显著作用的;在湿度为A2的情形下,温度对产出无显著作用。 11.5 本章小结 方差分析是一种常见的统计模型,用于检验样本间均值是否相等。 方差分析适用于处理因素类型为分类变量、响应变量类型为连续的情 形。根据因素个数,方差分析可以分为单因素方差分析与多因素方差分 析。在多因素方差分析中,要特别注意判断因素间是否存在交互作用。 此外,在实际应用中,可以通过设计合理的试验,在尽可能排除外部因 素的干扰后,再对试验数据进行方差分析,这样结果会更加准确。 第12章 主成分分析与因子分析 本章首先介绍主成分分析(Principal Component Analysis, PCA),然后简单介绍因子分析(Factor Analysis)。因子分析可以看 成是主成分分析的推广,其目的是在众多变量中,找出若干隐藏在这些 变量背后的“公共信息”。 12.1 主成分分析概述 本节主要介绍主成分分析的理论知识,包含主成分分析基本思想, 主成分的几何与代数意义,主成分的定义、计算与确定。 12.1.1 主成分分析的基本思想 1.主成分分析的基本思想 在实际应用中,为了能够完整地收集到所关心事物或问题的信息, 往往要从多个角度对多个变量的值进行采集,以进行分析,少则数个, 多则几十上百个,甚至更多。变量越多,对事物特征的反映就越完整、 准确,但同时也给数据的分析带来一定的困难:大量描述同一事物特征 的变量数据叠加在一起可能造成信息严重重复,甚至会掩盖事物内部的 真正规律。主成分分析的作用就是从现有的众多变量中,得出若干个起 主导作用的综合指标(通俗地说,这些综合指标就是主成分),并且可 以判定这些综合指标也就是主成分对所研究的事物或问题所起作用的大 小。通过对主成分的研究,既可抓住原始变量所表达的重要信息,又减 少了需要关心的变量数量,使得实际的应用和操作得到简化。 2.主成分分析的几何与代数意义 为了便于读者理解,下面从几何的角度直观地介绍主成分分析。假 设某数据包含N个观测,每条观测包含了两个变量X1,X2的取值,我们 可以将这些观测在X1,X2组成的坐标空间中用图描出来,如图12.1所 示。 图12.1 观测在空间的分布 从图12.1可以看出,观测分布于由X1,X2组成的坐标空间内的一个 椭圆中。众所周知,椭圆有一个长轴和一个短轴,长轴和短轴之间互相 垂直。例如,将图12.1中的坐标轴X1与X2旋转即可得到椭圆的长轴、短 轴Y1与Y2。沿短轴方向数据变化的范围较小,沿长轴方向数据变化的范 围较大。假设短轴的长度很小,那么只需考虑数据在长轴方向的变化, 就可以知道数据集的大致变化范围。例如,在图12.1中,可以仅考虑数 据在长轴Y1上的变化来近似原始数据集的变化,以达到简化分析的目 的。 当变量为3个时,所处理的对象即为三维空间上的椭球。与上述二 维空间的情况类似,我们可以找出对应椭球的主轴,利用长度最长的几 个主轴来代替原始变量进行研究。 总之,从几何角度看,可将原始变量进行旋转,使得旋转后的新向 量: ·两两之间垂直。 ·数据在某些新变量上的变化范围较大,在其余新变量上的波动范 围较小。 当变量个数大于3个时,无法从直观上想象,这时候就需要借助于 代数方法了。 首先,从代数角度看,向量选择可以通过对原始变量的线性变换来 实现。所谓线性变换,简单地讲就是对原始变量进行加、减、比例的放 缩等计算,以生成新的变量。例如,图12.1中,Y1与Y2可以通过以下X1 与X2的线性变换得到: 将上述系统以矩阵的形式表示出来就是: Y=AX 其中, 以及 其次,几何上的相互垂直表现为代数,即为变量间是线性无关的 (需要注意的是线性无关并不意味着垂直),即一个变量无法通过其余 变量的线性组合得到。统计学上,变量X1与X2之间的相关性可以通过协 方差来衡量,记号为COV(X1,X2),具体公式如下: COV(X1,X2)=[(X1-E(X1))(X2-E(X2))] 如果X1与X2线性无关,那么COV(X1,X2)=COV(X2,X1) =0。当X1与X2为同一个变量时,COV(X1,X2)为该变量的方差,即 Var(X1)或Var(X2)。将变量两两的协方差以矩阵的形式表示出来, 即为协方差矩阵,记号为∑。考虑变量个数为两个的情形,它们的协方 差矩阵如下: 综合上述3个方面,主成分分析的目的是找出一个矩阵A,使得对 X=(X1,X2,…,Xp)进行线性变换Y=AX后,得到的新的向量 Y=(Y1,Y2,…,Yp)的协方差矩阵为对角线矩阵,且该协方差矩阵 具有如下性质: ·在非对角线位置的取值为0。 ·在对角线位置的取值非0,且前几个对角线位置的值较大、后几个 对角线位置的取值相对较小。 12.1.2 主成分的定义、计算与确定 1.主成分分析的定义与计算 现在来看看有关主成分的严格定义。假设某待分析的数据中每个完 整的观测有p个变量,分别用X1,X2,…,Xp表示,这个p个变量构成 了p维的随机向量X=(X1,X2,…,Xp)。每一个观测对应值是随机变 量X的一个取值。∑为随机向量X的协方差阵。那么根据高等代数的理论 可知,一定存在正交矩阵U,使得 U'∑U=Λ 其中Λ为对角矩阵diag(λ1,λ2,…,λp),并且λ1≥λ2≥…≥λp≥0。 这时对X进行如下线性变换,使得: Y=U'X 可以得到新的随机变量Y=(Y1,Y2,…,Yp),其方差阵为 U'∑U=Λ,随机变量Y的各个分量Y1,Y2,…,Yp是互不相关的,并且 Yi的方差为λi。此时我们称Yi为(关于随机向量X)的第i个主成分 (i=1,2,…,p)。 熟悉高等代数的读者很容易可以由U'∑U=Λ看出,λi为∑的特征值。 在本章提及特征值时,即指此处的λi。 我们称数值 Yk的累积贡献率为 为主成分Yi的贡献率。前k个分量Y1,Y2,…, 该值也称累积解释变异的比例。 如果在λ1,λ2,…,λp中,有一部分值为零,也就是λr=λr+1=… =λp=0,那么就明确意味着Yr,Yr+1,…,Yp为常量,无变异,在统计 分析中可以忽略。 2.主成分个数的确定 主成分个数的确定是主成分分析中关键的一步。一般来说,确定主 成分个数的方法有:特征值大于1准则、陡坡检验法(Scree Test)以及 累积解释变异的比例法。下面具体介绍这3种方法。 在主成分分析中,用于确定主成分的最常见方法就是特征值大于1 准则,该准则又称Kaiser准则。根据该准则,在主成分分析中,我们仅 保留特征值大于1的主成分。该准则背后的思想为,每个原始数据中的 每一个变量“贡献”了至少1个单位的变异,当主成分对应的特征值大于1 时,该主成分包含2个或者2个以上原始变量的变异信息,选择这样的主 成分能够以较少个数的主成份保留原始变量的绝大多数信息。 陡坡检验法是将主成分与其对应的特征值在二维坐标轴中显示出 来,这种图形一般称为陡坡图(Scree Plot)。理想状态下的陡坡图先是 一段陡峭的曲线,然后是一段较为平坦的曲线。在利用陡坡图判断保留 主成分个数时,仅需保留曲线在平坦趋势的第一个点之前的主成分即 可。从图12.2中可以看出,在主成分3之后曲线变得平坦。因此,仅需 保留3之前的因子,即主成分1和2。 图12.2 陡坡图 考虑累积解释变异的比例也是常见的判定主成分个数的方法之一。 一般来说,第一主成分能解释变异的比例最大,其次为第二、第三,以 此类推。该方法的原理是在当前所有的主成分累积变异比例达到一定程 度时,我们就认为当前的主成分已经包含原始变量中足够多的信息,可 以用来代替原始数据中的所有变量。通常来说,累积变异的比例应该要 达到70%以上。 在实际应用中,应该具体问题具体分析,确定主成分个数没有通用 的准则,建议读者综合上述3种方法以及研究问题的背景来判断主成分 的个数。 12.1.3 主成分分析难点探讨 顾名思义,主成分分析就是寻找主成分,但是,不能单纯为了简化 数据、便于解释,就在没有考虑问题背景与数据本身结构特点的情况下 盲目地进行主成分分析。下面探讨下主成分分析过程中的一些难点。 难点一:协方差矩阵与相关系数矩阵 在主成分分析的实际计算过程中,有两个矩阵起着至关重要的作 用,它们是相关系数矩阵与协方差矩阵。使用不同的矩阵计算出来的结 果差异有可能会很大。因此,有必要了解二者之间的区别。 首先回顾一下相关系数的概念。在11.4节介绍双因素分析时,提到 了两个因素之间可能相关。衡量两因素(或者变量)X1与X2间相关的统 计量为皮尔逊相关系数(Pearson Correlation Coefficient)。将变量两两 间的相关系数以矩阵的形式表示就形成了相关系数矩阵(Correlation Matrix)。与相关系数类似,统计学上还有一个统计量用于衡量两个变 量间相关性,称为协方差系数(Covariance Coefficient)。与协方差系 数对应的矩阵称为协方差矩阵(Covariance Matrix)。二者的计算公式 如下: 从计算公式中可以看出,二者的区别在于是否对变量进行了标准化 (标准化的结果是变量的方差变为1):将原始变量标准化后得到的协 方差矩阵即为相关系数矩阵。相关系数矩阵削弱了单个指标的方差、保 留了指标间的相关性。因此,我们可以结合数据本身的特点来选择是从 协方差矩阵出发还是相关系数矩阵出发计算主成分。 一般而言,对度量单位不同的变量或者是取值范围差异非常大的变 量,不应直接从协方差矩阵出发分析,而应该考虑标准化后的数据,即 相关系数矩阵。例如,在对某上市公司的数据进行分析时发现,其销售 额的取值在几亿到几十亿、利润从1千万到几千万不等、市盈率在几百 间波动、每股净利润在几块钱左右,不同的变量取值差别相当大。这 时,如果不进行标准化、直接对数据进行主成分分析,就会发现销售额 在主成分分析中起到了“统治”作用,而其他指标很难在分析中体现出 来。总之,如果要避免数据中单个变量对主成分分析产生负面影响,对 此,可以考虑相关系数矩阵。 反之,如果要突出单个指标在分析中的作用,可以考虑从协方差矩 阵出发进行计算。具体地说,如果原始数据中的指标在分析中所占的权 重不一样,则可以考虑从协方差矩阵出发进行主成分的计算。例如,为 了了解影响课堂教学质量的不同因素的重要性,通过随机问卷调查的方 式对某高一年级的学生进行了调查,问卷调查的内容包含对以下几个指 标进行打分,分数越高代表该指标越重要:课前教师布置预习任务;课 堂提问;课堂互动;使用多媒体教学;课堂练习;课堂中分组讨论;教 师对课程的教学要求、语言鼓励性。现假设上述指标间的重要性不一 样,这种情形下,就可以考虑从协方差矩阵出发进行主成分分析。 总之,进行主成分分析前,应充分考虑问题的背景与数据的结构特 征。建议在实际工作中,分别从不同的角度出发求解主成分并研究结果 的差异,找出产生明显差异的原因,以确定哪个方法更可信。 难点二:主成分个数的确定 在某些情形下,主成分个数的确定也可能是分析过程的难点之一。 根据特征根大于1准则,我们应保留所有特征值大于1的主成分,但是, 这是否意味着拒绝对应特征值为0.999的主成分一定对分析有利?类似 的,接受对应特征值比1略大的主成分也不一定对分析有利。在陡坡图 中,有时候主成分之间在垂直方向的落差几乎相等,在这种情况下,依 据该准则也不能准确地判定主成分个数。用累积变异个数来确定主成分 个数时也会存在类似的情况:一般认为70%的变异是足够的,但到底多 少最合适并没有定论。 事实上,正如上面指出的一样,没有哪一种准则是通用的。在处理 主成分个数的问题中,如果遇到个数不是很明显的情形,可以综合考虑 以上几种方法进行判定。例如,在是否接受特征值为1左右的主成分问 题上,可以参考当前累积变异;如果接受该主成分对累积变异的比例贡 献不大,则可以不接纳该主成分。最后,对主成分个数的判定不能脱离 研究问题的背景。例如,假设当前主成分主由若干个变量组成,这些变 量代表一种综合结构、指标(例如,若干个变量都是关于娱乐方面支出 或者教育方面的投入),且与其他综合指标并不重复,那么无论它的特 征值是否接近于1,无论累积变异比例多少,都应该将该综合指标考虑 进来。 12.2 使用SAS实现主成分分析 在SAS中,某种统计方法可能可以通过多个过程步实现。这时候有 必要了解过程步之间的区别。比如,主成分分析就可以通过PROC FACTOR或PROC PRINCOMP实现。下面就来比较二者之间的区别。 12.2.1 FACTOR过程与PRINCOMP过程的比较 过程步PROC FACTOR不仅可以进行主成分分析,还可以用来做因 子分析,其默认方法是主成分分析。使用PROC FACTOR与PROC PRINCOMP进行主成分分析时,除了因子得分系数矩阵有区别外,二者 的输出结果几乎一致。至于因子得分系数矩阵的区别,具体来说指的 是:在PROC FACTOR中,主成分在各个变量上的得分系数的方差是标 准单位1,而在PROC PRINCOMP中,主成分在各个变量上的得分系数 的方差是其对应的特征值(具体见12.2.3节例12.2)。二者均支持通过 ODS图形选项输出图形。 同PROC FACTOR相比,PROC PRINCOMP具有以下优点: ·当预期的主成分个数较少的时候,使用PROC PRINCOMP需要运 行的时间较少。 ·在内存一定的情况下,PROC PRINCOMP能处理的问题规模更大 一些。 ·PROC PRINCOMP对偏协方差矩阵或偏相关矩阵进行分析时会输 出相应的得分。 ·从使用角度来说,PROC PRINCOMP更简单容易掌握。 同PROC PRINCOMP相比,PROC FACTOR具有以下优点: ·从输出结果看,PROC FACTOR输出内容更丰富。 ·PROC FACTOR可以用来做因子旋转。 12.2.2 使用PRINCOMP过程进行主成分分析 使用PROC PRINCOMP进行主成分分析时,其输入可以是原始数据 集、协方差矩阵或相关矩阵等,其输出数据集包含特征根、特征向量以 及标准化或未标准化的主成分得分。此外,使用者还可以通过ODS图像 选项输出陡坡图(Scree Plot)、成分特征图(Component Pattern Plot) 等图形,这些图形都是进行主成分分析的有用工具。过程步PROC PRINCOMP的一般形式如下: PROC PRINCOMP<选项>; BY变量; VAR变量; RUN; 其中: ·PROC PRINCOMP语句中常见的选项如表12.1所示。 表12.1 PROC PRINCOMP常见的选项及含义 ·BY语句指定分组变量。PROC PRINCOMP根据BY语句中的变量对 原数据进行分组分析。若BY语句中的变量多于一个,那么仅最后一个 变量起作用。该语句要求原始数据已按照BY语句中的变量排序。 ·VAR语句指定数据集中用来进行主成分分析的变量,这些指定变 量类型必须为数值型。 例12.1:数据集sashelp.cars包含不同型号的汽车的一些参数,共有 15个变量以及428条观测,具体变量的含义如表12.2所示。现在要根据 数据集sashelp.cars中的变量MPG_City、MPG_Highway、Weight、 Wheelbase以及Length,对其进行主成分分析。 表12.2 数据集sashelp.cars中的变量具体信息 示例代码如下: proc princomp data = sashelp.cars out = car_component; var MPG_City MPG_Highway Weight Wheelbase Length; run; 程序的输出结果中包含了数据集的一些简单统计量,具体如图12.3 所示。 图12.3 数据集sashelp.cars的简单统计量 紧接着是相关矩阵以及该矩阵对应的特征值,如图12.4所示。 图12.4 相关矩阵以及其特征值 从相关矩阵中可以看出:变量MPG_Highway与MPG_City之间有很 强的相关性;变量Weight、Wheelbase和Length两两之间的相关性也较 强。 从相关矩阵的特征值表中可以看出:对应于特征值3.7296的主成分 能解释74.59%的变异;对应于特征值0.9161的主成分能解释全部变异的 比例为18.32%;前两个主成分能解释的变异比例达到了92.91%。 接下来输出的是特征向量表,该表给出的是对应上述特征值的特征 向量,如图12.5所示。 在讲述主成分分析的原理时,曾介绍过主成分分析是通过对原始变 量的线性组合生成若干个主成分的。现在从特征向量表中,可以得到每 一个主成分线性组合的具体信息,例如,从该表可以知道第一主成分 为: Prin1=-.443657*MPG_City-.448585*MPG_Highway+0.479373*Weight+0.439937* 从这个角度说,主成分仍然是可观测的,可以通过观测主成分中各 个变量的值,再使用线性组合的信息得出主成分的信息(对比在下文因 子分析中,因子是不可观测的)。此外,每一条观测带入上述式子即可 得到该观测在第一主成分上的得分。 图12.5 对应相关矩阵特征值的特征向量 最后,如果ODS GRAPHICS选项处于打开状态,过程步还会输出陡 坡图和方差解释图,如图12.6所示。陡坡图在上一节中已经讲述了,这 里就不再重复。方差解释图反映的是累积解释方差比例的信息。 图12.6 PROCPRINCOMP输出的陡坡图和方差解释图 综合上面的分析可以用主成分Prin1和Prin2来代替原始数据集的变 量进行分析。除了上述输出结果外,过程步还生成了数据集 Work.Car_component。该数据集包括原始数据集的所有变量以及主成分 Prin1-Prin5(取值为对应观测在主成分上的得分),部分结果如图12.7 所示。 图12.7 数据集Work.Car_component 从图12.7可以看出,原始数据集的第一条观测在第一主成分Prin1上 的得分为1.0745,在第二主成分Prin2上的得分为-0.6622,以此类推。需 要指出的是,之所以在某个主成分上得分为负数,是因为在计算主成分 得分时使用了标准化后的原始数据集。 12.2.3 使用FACTOR过程进行主成分分析 除了PROC PRINCOMP外,还可以使用PROC FACTOR来进行主成 分分析。事实上,在进行标准化后,二者的结果是一样的。为了比较二 者的结果,首先介绍如何对数据进行标准化。SAS对数据的标准化是通 过PROC STDIZE实现的,PROC STDIZE的一般形式如下: PROC STDIZE DATA =数据集 METHOD = 标准化方法; VAR 变量; RUN; 其中: ·选项METHOD指定用于标准化的方法。常见的标准化方法有 MEAN、MEDIAN、SUM、EUCLEN和STD。 ·VAR语句指定数据集中用来进行主成分分析的变量,变量类型必 须为数值型。若该语句缺失,那么PROC FACTOR将分析数据集中的所 有数值型变量。 变量的计算方法如下: 新的变量值=(原来的变量值-LOCATION)/SCALE 这里LOCATION和SCALE的值与标准化方法有关。表12.3列举了一 些常见的标准化方法的LOCATION和SCALE值。有关其他方法的具体 参数值建议读者参考SAS官方帮助文档。 表12.3 常见标准化方法中的LOCATION值与SCALE值 这里仅简单介绍PROC FACTOR中与主成分分析相关部分的选项, 在下文使用PROC FACTOR进行因子分析时,会对其他选项进行介绍。 PROC FACTOR的语法如下: PROC FACTOR<选项>; VAR变量; RUN; 其中: ·常见的选项有:DATA=用于指定输入数据集,SIMPLE输出常见 的统计量,CORR输出原始变量的相关矩阵。 ·VAR语句指定数据集中用于分析的变量。 例12.2:使用PROC FACTOR对数据集sashelp.cars进行主成分分 析。 示例代码如下: proc factor data = sashelp.cars simple corr; var MPG_City MPG_Highway Weight Wheelbase Length; run; 输出结果中基本统计量与相关矩阵的部分如图12.8所示。 图12.8 输出基本统计量与相关矩阵 同时,PROC FACTOR还输出了相关矩阵的特征值与解释的变异比 例,这部分内容也和PROC PRINCOMP一致,如图12.9所示。由该表可 以判定主成分个数为1或者2。 图12.9 相关矩阵特征值以及解释变异的比例 除此之外,PROC FACTOR还输出了公共因子信息,这是PROC PRINCOMP所没有的。但是,二者在本质上是一样的。为了便于比较, 这里将因子(这里可以理解为主成分)个数定为2个,代码修改如下: proc factor data = sashelp.cars simple corr n=2; var MPG_City MPG_Highway Weight Wheelbase Length; run; 这里的关键字N=2限定了输出因子的个数为2。输出结果与修改前 大致一致,只是有关因子方面的信息发生了变化,具体如图12.10所 示。 图12.10 指定输出因子的信息 如果将上述结果的Factor1与Factor2分别看成第一主成分与第二主成 分,那么因子模式图提供了主成分由原始变量线性组合的信息。例如, 第一主成分为: Factor1=-0.85680*MPG_City0.86632*MPG_Highway+0.92577*Weight+0.84961*Wheelbase+0.81615*Length 至此,我们利用PROC FACTOR完成了对原始数据的主成分分析: 找出了主成分的个数为2个,并且知道了如何通过原始变量的线性组合 得到主成分。 虽然通过过程步PROC FACTOR和PROC PRINCOMP得到的主成分 个数一样,解释的方差也一致,但是主成分的构成不一样。回顾例 12.1,在该例中,第一主成分为: Prin1=-0.443657*MPG_City0.448585*MPG_Highway+0.479373*Weight+0.439937*Wheelbase+0.422608*Length 导致二者差异的主要原因是PROC FACTOR会假设所有的因子或主 成分的方差为1,而PROC PRINCOMP则假设所有主成分的方差为特征 值。如果要两者要得到一样的结果,只需要对PROC FACTOR输出的得 分矩阵进行标准化,然后对标准化后的得分矩阵进行缩放即可,缩放的 比例为 这里N为变量个数。这一标准化过程以及缩放可以通过 PROC STDIZE来实现。例如,在本例中,缩放的比例为 如下: proc factor data=sashelp.cars n=5 score; ods output StdScoreCoef=Coef; var MPG_City MPG_Highway Weight Wheelbase Length; run; proc stdize method=ustd mult=0.44721 data=Coef out=work.eigenvectors; Var Factor1-Factor5; run; proc print data=work.eigenvectors; run 数据集work.eigenvectors的输出结果如图12.11所示。 代码 该结果和例12.1中的结果一致。 综上所述,利用PROC FACTOR和PROC PRINCOMP进行主成分分 析从本质上讲是一样的。 图12.11 标准化后的得分矩阵 12.3 因子分析概述 前面讨论的主成分分析是对现有的随机变量通过线性变换生成新的 随机变量,由于新生成的随机变量是按照对变异贡献的大小排序的,因 此仅仅考察前几个主成分就可以实现分析目的,使问题得到了简化。因 子分析的目的也是寻找潜在的少数几个(少于原始随机变量数目)新的 变量,以便在实际工作中可以采取更合理的方案或措施,并揭示隐藏在 数据中的基本规律。 主成分分析考察和解释的是方差,也就是数据的变异程度,而因子 分析却是从随机变量协方差也就是相关性的角度进行研究的。 12.3.1 公共因子与特殊因子 因子就是前面所说的试图寻找到的潜在的、个数较少、反映原始随 机变量相关性的新随机变量。前面介绍的主成分可以由原始变量线性表 示,是可以观测的。与主成分不同,因子往往不能像主成分一样,由原 始变量线性表示,因此是不可观测的。在因子分析中,因子可以分为公 共因子和特殊因子。对原始数据若干个指标都起作用的称为公共因子, 仅仅对某个指标起作用的称为特殊因子。例如,对若干名高中学生的语 文、数学与英语成绩进行分析,得知每个科目的成绩和一个变量都相 关,我们称这个变量为智力。这个变量“智力”是虚构出来的、不可观测 的,即因子。该因子反映了3个科目成绩的变异,因此,是一个公共因 子。每个科目的成绩除了和智力相关,可能还和其他因素相关。我们将 其他因素笼统地用另外一个虚拟变量来表示,这个虚拟变量称为特殊因 子。 假设X1、X2、X3分别对应学生的语文、数学与英语成绩。记公共 因子为F,特殊因子为ei(ei仅对科目Xi有作用)。那么,每个科目的成 绩都遵从以下形式: Xi=ai*F+ei(i=1,2,3) 对上述例子进行推广,假设每一个科目的成绩不止受到一个公共因 子的影响而是m个(可以认为m通常小于等于科目数):F1,…,Fm。 这时候,每个科目的成绩都遵从以下形式: Xi=ai1*F1+ai2*F2+…+aimFm+ei 不失一般性,这里假设:Xi为标准化后的成绩,即均值为0,方差 为1;F1,F2,…,Fm是彼此不相关的公共因子,均满足均值为0,方差 为1;ei为特殊因子,与每一个公共因子均不相关且均值为0;系数ai1, ai2,…,aim分别称为第i门考试成绩在F1,F2,…,Fm上的因子载荷。 对于上述模型,我们有 从上面的等式可以看出,方差可以分为两部分,一部分和公共因子 相关,即等式中的 称为Xi的共性方差,记为 另外一部分 var(ei)称为Xi的特殊度或者剩余方差。由于共性方差的总和不超过 1,因此,对于每一个因子载荷aij(j=1,2,…,m),我们 有-1≤aij≤1。一般情况下,因子载荷aij的绝对值越大,代表Xi与因子Fi的 相互依赖程度越强。由因子载荷aij组成的矩阵称为因子载荷矩阵。 12.3.2 因子分析的计算过程 因子分析过程可以分为:因子载荷计算、因子旋转与因子得分3个 部分。因子载荷的计算方法主要有主成分法、主轴因子法、最小二乘 法、极大似然法以及α因子提取法。这些方法求解的出发点不同,所得 的结果也不尽相同。在对因子载荷进行计算后,就要结合求解问题的背 景去分析公共因子的意义了。如果公共因子的实际意义不明显,可以尝 试着进行因子旋转。经过旋转,因子的结构会发生变化。值得一提的 是,旋转发生变化不代表先前的因子结构是错误的,二者仅表示看问题 的角度不同而已。 1.因子载荷计算 在众多的因子载荷计算方法中,哪种方法最有效并无定论。这里仅 介绍主成分分析法与极大似然法。 下面首先介绍如何利用主成分分析法进行因子载荷计算。用主成分 法确定因子载荷时首先要对数据进行一次主成分分析,将前面的几个主 成分作为未旋转的公共因子。使用该方法的优点是对公共因子与特殊因 子的分布均无假设要求;缺点是计算出来的特殊因子之间是相关的。不 过,当共性方差较大时,特殊因子之间相关性带来的影响几乎是可以忽 略的。因此,很多有经验的分析人员在进行因子分析的时候总是先尝试 使用主成分分析,然后再尝试使用其他方法。与主成分分析法不同,极 大似然法需要假定公共因子和特殊因子服从正态分布,通过构造极大似 然函数来对因子载荷与特殊因子方差进行极大似然估计。 如果因子满足正态性假设,读者可以先尝试使用极大似然法;否则 建议尝试使用主成分分析法,该方法对分布并无要求。 2.因子旋转 进行因子载荷计算的最终目的不仅在于寻找公共因子,更重要的是 对公共因子的意义进行分析。理想情况下,我们希望看到变量在某单个 因子上具有高额载荷,而在其余因子上有较小的载荷。例如,通过对例 12.1中的数据集sashelp.cars进行因子分析(具体见12.4节的例12.3),我 们抽取了两个公共因子,具体如图12.12所示。 从图12.12可以看出,除了变量Weight(仅在Factor1上有高额载 荷)以外,其他变量在两个因子上均有明显的载荷。因此,很难分析出 因子Factor1与Factor2的实际意义。 图12.13显示的是经过选择后的因子模式图。从该图中可以看出, 经过旋转,因子有了明显的“改进”:变量MPG_City和变量 MPG_Highway仅在因子Factor1上有高额载荷,变量Wheelbase和变量 Length仅在因子Factor2上有高额载荷。这对我们分析因子的实际意义是 很有帮助的。例如,由于变量MPG_City和变量MPG_Highway仅在因子 Factor1上有高额载荷,因此可以将二者结合起来考虑,例如可以将因子 Factor1解释成“汽车整体油耗”。 图12.12 未经旋转的因子模式图 图12.13 旋转后的因子模式图 因子旋转的方法很多,大体上可以分为正交旋转和斜交旋转。所谓 正交旋转是由初始载荷矩阵A右乘以正交矩阵。经过正交旋转得到的新 的公共因子仍然保持彼此不相关的性质(例如,图12.1中Y1与Y2可以看 成是X1与X2经过正交旋转得到的)。而斜交旋转则是放松了因子彼此不 相关这一限制,因而得到的公共因子可能是相互关联的。实现正交旋转 最常用的方法是方差最大正交旋转法,该方法是由H.K.Kaiser最先提出 的。该方法的基本思想是使每个因子中包含高载荷变量的个数最少。实 现斜交旋转中最常用的方法是Promax法,该方法由Hendrickson等人于 1964年提出,原理是先进行正交旋转,找到一个方差最大的正交解,接 着在该正交解的基础上进行斜交旋转。Promax法速度快,适用于数据量 较大的情形。 采用正交旋转还是斜交旋转的根本依据是使用者是否需要公共因子 之间是彼此不相关的。一般来说,采用这两种旋转方法就可以达到不错 的效果。下文会介绍如何在SAS中指定旋转方法。限于篇幅,其他旋转 方法就不一一介绍,有兴趣的读者可以进一步查阅相关的SAS官方文 档。 3.因子得分 在因子分析中,我们最终感兴趣的可能是因子的得分。例如,假设 对sashelp.cars进行分析后,得到了两个因子:F1与F2。如果要计算数据 中每一条观测在因子F1与F2上的得分就会涉及因子得分。在主成分分析 中,也有过类似的“得分”概念。需要指出的是,二者其实是不一样的。 在主成分分析中,主成分是原始变量的线性组合(例12.1中的主成分 Prin1),在考虑所有主成分的情况下,主成分与原始变量之间的关系是 可逆的。在因子分析中,公共因子与原始变量的关系是不可逆的,不过 它们之间的关系可以通过回归得到。在得到相应的关系后,就可以根据 原始数据的观测在变量上的取值来计算该观测在公共因子上的得分了。 12.3.3 因子分析与主成分分析比较 主成分分析和因子分析有很多类似之处,这也是二者容易混淆的原 因之一。从过程上看,无论是主成分分析还是因子分析都会试图“降 维”,即找到数量较少的新的变量来对数据进行分析。不同的是,对主 成分分析而言,找到个数减少的变量后,分析也随之结束了;而对因子 分析而言,“降维”仅仅是一个过程,最终目的是找出能解释原始变量的 公共因子和特殊因子。这就是主成分分析和因子分析最大的不同。从这 个角度看,因子分析是主成分分析的推广。 除此之外,主成分分析和因子分析还有如下不同: ·因子分析中,原始变量表示成各个因子的线性组合;主成分分析 则是把主成分表示成各个原始变量的线性组合。 ·主成分分析不需要假设,而因子分析则需要一些假设条件。因子 分析的假设包括:各个公共因子不相关、特殊因子不相关、公共因子和 特殊因子不相关等。 ·在主成分分析中,当给定的相关系数矩阵或者协方差矩阵的特征 值唯一时,主成分一般是固定的;而在因子分析中,因子不是固定的, 通过旋转可以得到不同的因子。 由于初学者容易混淆二者,下面举一些例子来说明二者的区别,如 表12.4所示。 表12.4 主成分分析与因子分析的适用情形 总之,在判断是使用因子分析还是主成分分析时,可以结合分析的 具体目的来考量:如果分析目的仅仅是为了减少分析变量的个数,那么 可以采用主成分分析;否则,可以考虑因子分析,因子分析的公共因子 通常有着非常现实的意义。 12.4 使用SAS实现因子分析 使用PROC FACTOR对数据进行因子分析时,其输入可以是原始数 据集、相关系数矩阵、协方差矩阵,还可以是得分系数矩阵。因子载荷 的计算方法包括主成分法、极大似然法、α因子分析和未加权的最小二 乘因子分析法等。因子旋转的方法涵盖正交旋转与斜交选择。此外, PROC FACTOR的输出也很丰富,有相关矩阵、方差等常见的统计量, 还有特征值、特征根以及因子解释的比例,甚至包括陡坡图等图形的输 出。 默认情况下,PROC FACTOR会对输入数据集进行主成分分析,因 此,若想要使用PROC FACTOR进行因子分析,必须为进行分析的每个 变量设置一个共性方差值。过程步PROC FACTOR的一般形式如下: PROC FACTOR<选项>; VAR变量; PRIORS共性方差; PARTIAL 变量; FREQ 变量; BY变量; RUN; 其中: ·语句PROC FACTOR中涉及的常见选项如表12.5所示。 ·PRIORS语句指定VAR语句中变量的共性方差值。 ·PARTIAL语句指定分析中使用偏相关系数矩阵或者协方差矩阵 (Partial Correlation or Covariance Matrix)的变量。 ·FREQ语句指定原始数据集的某个变量,该变量值为对应观测的频 数。假设某条观测对应的频数为k,那么分析时,PROC FACTOR会假 定该观测在数据集中总共出现了k次。 ·BY语句指定分组变量。PROC PRINCOMP会根据BY语句中的变量 对原数据进行分组分析。若BY语句中的变量多于一个,那么仅最后一 个变量起作用。使用BY语句时要求数据已按照BY语句中变量的顺序排 序。 表12.5 PROC FACTOR语句常见的选项列表 例12.3:使用PROC FACTOR对sashelp.cars中的变量MPG_City、 MPG_Highway、Weight、Wheelbase及Length进行因子分析。 为了使用PROC FACTOR进行因子分析,需要对PRIORS进行设 定,可以将其值设为SMC。至于因子旋转方法,这里考虑使用方差最大 正交旋转方法。示例代码如下: proc factor data=sashelp.cars corr priors=smc rotate=varimax; Var MPG_City MPG_Highway Weight Wheelbase Length; run; 输出结果包括3个部分:基本统计量、初始因子解和旋转因子解。 下面结合代码逐一分析。在基本统计量部分,由于关键字CORR的使 用,PROC FACTOR输出了相关矩阵。整个基本统计量输出如图12.14所 示。 图12.14 PROC FACTOR输出的相关矩阵 代码中,指定了PRIORS为SMC。对应这部分的输出如图12.15所 示。 图12.15 选项PRIORS=的部分输出 其余部分是有关因子模式的输出。旋转前后因子对比如图12.16所 示。 图12.16 旋转前后因子对比 由于我们采用的是方差最大正交旋转方法,因此旋转后的因子仍然 为正交。从图12.16中可以看出,在旋转前,除了变量Weight,其余变量 在Factor1和Factor2上均有载荷,而且载荷明显;经过旋转后,除了变量 Weight,其余变量则均在某一因子上有高载荷,而在另一因子上的载荷 低(这正是我们希望看到的)。 遗憾的是,本例中变量Weight在两个公共因子上的载荷相当,因 此,可以把Weight归入公共因子Factor1中,当然也可以考虑归入 Factor2。公共因子Factor1包含两个高载荷的变量MPG_City和 MPG_Highway,它们均描述的是油耗方面的信息,因此,可以将公共 因子Factor1解释为“整车油耗”。同理,通过对Factor2高载荷变量的研 究,可以将公共因子Factor2解释成“整车舒适性”(变量Wheelbase和变 量Length反映是车内空间大小,变量Weight取值越大的车子驾驶起来越 稳定,三者从不同角度解释了车子的舒适性)。 需要注意的是,采用不同的方法得出的公共因子结构可能不一样。 即使是同样的公共因子,由于人的主观因素,解释的结果也可能不一 样。但是,只要解释合理,分析结果就是合理的。 在12.3.2节中提及了找出公共因子可能不是分析的最终目的。在找 出公共因子后,我们可以对数据集中的观测进行得分计算(即确定因子 得分)。因子得分计算可以通过在PROC FACTOR语句中指定 NFACTORS=选项和OUT=选项来实现;或者使用过程步PROC SCORE 来间接实现。PROC SCORE的一般形式如下: PROC SCORE DATA=数据集<选项>; VAR 变量; RUN; 其中: ·DATA=指定用于计算得分的原始数据集。 ·PROC SCORE语句中常见的选项有:OUT=指定输出数据集, SCORE=指定包含因子得分系数的数据集。 ·VAR语句指定原始数据集中用于计算得分的变量。 在下面的例子中,使用SCORE过程间接实现了对FACTOR过程的因 子得分计算。 例12.4:使用PROC FACTOR,根据数据sashelp.cars进行公因子提 取与因子得分计算。 在下面的代码中,首先使用PROC FACTOR对数据进行公因子的提 取、旋转,并将各种统计量包括因子得分系数输出到数据集 work.fact_cars中;接下来,利用PROC SCORE,根据work.fact_cars对原 始数据集观测进行得分计算,并最终将结果输出到数据集work.Fscore。 proc factor data=sashelp.cars priors=smc rotate=varimax outstat=work.fact_cars score; var MPG_City MPG_Highway Weight Wheelbase Length; run; proc score data = sashelp.cars score=work.fact_cars out=work.Fscore; var MPG_City MPG_Highway Weight Wheelbase Length; run; 上述代码中PROC FACTOR部分的输出结果与例12.3几乎一样,这 里不重复解释。数据集work.fact_cars如图12.17所示。 图12.17 数据集Work.Fact_cars 最终,通过SCORE过程计算出的得分数据集work.Fscore的部分结果 如图12.18所示。 从数据集work.Fscore可以读出原始数据集每条观测在公共因子 FACTOR1和FACTOR2上面的得分。与主成分得分一样,上述数据集中 之所以会出现负分,是因为计算得分使用的是标准化后的原始数据集。 图12.18 得分数据集work.Fscore部分结果 12.5 本章小结 主成分分析可以用来对复杂数据进行降维分析;因子分析可以找出 隐藏在众多变量后的公共因子。公共因子一般有着现实意义,公共因子 可以用来对数据中的观测进行得分计算。读者可以根据分析的目的确定 是选择主成分分析还是因子分析。无论采用哪一种分析方法都应该结合 原数据集的背景和特征考虑。 第13章 聚类分析 聚类分析的目的就是将所关心的对象按照一定的规则或标准分成不 同的类别,以便有针对性地进行进一步有效的处理。从技术和数据的角 度讲,聚类分析就是利用数理统计的方法对数据的变量或观测进行分 类。在进行聚类分析之前,往往不知道所考察的对象会存在哪些类别。 聚类分析广泛应用于服务业、生物、人口统计学等领域。在服务 业,聚类分析用来分析、发现不同的客户群,并刻画出不同客户群的特 征;在生物学上,聚类分析用来对新发现的物种进行属性的归类;在人 口统计学上,分析人员利用聚类分析对地域进行划分,进而对不同类型 的地域制定出合适的政策。此外,聚类分析还被成功应用于其他算法的 预处理步骤:待聚类结果产生后,再将其他算法应用于每个簇上。 13.1 聚类分析的概述 聚类大致可以分为以下两类: ·模糊聚类(Fuzzy Clustering or Soft Clustering) ·非模糊聚类(Hard Clustering) 在非模糊聚类中,对象与类的从属关系都是确定的:属于或者不属 于。在模糊聚类中对象与类的从属关系则是有一定的概率的。本章考虑 的是非模糊聚类且聚类对象为离散的情况,也称离散聚类(Discrete Clustering)。 13.1.1 聚类分析方法介绍与比较 常见的聚类分析方法有以下两种: ·层次法(Hierarchical Clustering) ·划分法(Partitive Clustering) 其中,层次法又可分为:凝聚式(Agglomerative)和分裂式 (Divisive)。凝聚式层次法指的是先将每个观测都归为一类,然后每 次都将最相似的两个类合并成一个新的类,直至所有的观测成为一类或 者达到所预定的分类条件为止;反之,分裂式层次法在聚类开始时就会 将所有观测归为一类,接下来每次都把现有的类别按照相似程度一分为 二,直至每一观测都各自成为一类或者达到所预定的分类条件为止。 SAS中的层次法都是凝聚式。因此,除非特别说明,本章中的层次法均 指凝聚式层次法。 划分法是指在开始阶段指定某几个类中心,接下来通过计算将每个 观测暂时归到距离其最近的类中心所在的类,并且不断调整类中心直至 收敛。表13.1对层次法和划分法进行了对比。 表13.1 划分法与层次法的对比 衡量一个聚类分析方法的好坏主要有以下两个方面: ·在同一类别的内部,对象是否具有高度的相似性。 ·不同类别的对象间是否几乎不具有相似性。 13.1.2 相似性的度量 前面介绍了如何衡量一个聚类方法,其中涉及一个核心的问题:如 何度量两个对象间的相似性。一般情况下,使用距离函数d(x,y)来 表示对象x与y间的距离,其值越小表示对象越相似。对于同一组研究对 象,定义距离的方法可以有很多。例如,把一副52张(不含大小王)的 扑克牌进行分类,我们可以定义以下两种距离: ·距离一:根据牌面上的花色定义,同一花色的牌之间的距离为0, 不同花色牌之间的距离为1。 ·距离二:根据牌面上数值的大小(对应1~13)之差来定义。 当然,读者还可以定义出更多的距离。虽然,距离定义的方法很 多,距离定义的合理与否从根本上决定了聚类分析的效果。 通常,一个好的距离定义一般满足以下4个条件(这里x、y和z均表 示对象)。 ·具有对称性:d(x,y)=d(y,x) ·若x≠y,则d(x,y)>0 ·若x=y,那么d(x,y)=0 ·三角不等式:d(x,y)≤d(x,z)+d(z,y) 上述第一个条件可以简单地理解为对象x与对象y的相似程度应该等 于对象y与对象x的相似程度;接下来两个条件指的是:若x和y是两个不 同的对象,则它们之间距离应该大于0,反之,如果它们是相同的对 象,那么距离应该是0;最后一个条件“三角不等式”的作用是从数学的 角度把度量限制在欧式空间,通俗地说,对象x与y之间的距离只能是直 线,而不能是不规则的曲线。 需要注意的是,实际中也有一些常见的、好的距离度量并不完全满 足上述4点。在定义距离时应该结合实际研究问题的背景。 常见的距离函数有以下这些: ·街区距离(City Block Distance) ·欧式距离(Euclidean Distance) ·闵可夫斯基距离(Minkowski Distance) ·汉明距离(Hamming Distance) 两点x=(x1,x2,…,xk)和y=(y1,y2,…,yk)的街区距离是 以线段xy为斜边的直角三角形的两直角边的距离和,其公式如下: 欧式距离是勾股定理在多维空间的一个推广。其一般形式下的公式 如下: 闵可夫斯基距离公式如下: 其中,λ的值为正数。街区距离和欧式距离都是闵可夫斯基距离的 特殊形式,二者分别对应λ值为1和2的情况。 上述距离都是用来处理连续变量的。汉明距离则是常见的用来度量 离散变量的距离之一。两个离散变量间的汉明距离定义为两个变量对应 位置的值不同的个数。 假设x=(0,1,1,0,0,1),y=(0,0,1,1,0,1)。在这种 情况下,x和y共有2个位置的值不一样,那么x和y间的汉明距离为2。 SAS允许用户自定义距离函数,经距离函数计算出的结果(SAS数 据集)可以作为其他聚类分析过程的输入数据使用。除用户自定义的距 离函数外,过程DISTANCE提供了多种计算数据集观测间距离的方法, 其一般形式如下: PROC DISTANCE<选项>; BY 变量; COPY变量; FREQ 变量; ID 变量; VARLEVEL(变量</选项>); WEIGHT变量; RUN; 其中: ·PROC DISTANCE语句中常见的选项有:DATA=(输入数据 集),OUT=(输出数据集),METHOD=(计算方法)。 ·PROC DISTANCE根据BY语句指定的变量对每组观测做独立的分 析,使用该语句的前提是数据集已经按照BY语句中的变量排序。 ·COPY语句指定了从输入数据集中复制到输出数据集的变量。 ·FREQ语句中的变量对距离的计算结果无直接影响,其作用是标准 化变量或者用来对顺序变量赋值。如果FREQ是用来标准化变量的,那 么变量值代表观测出现的频数。 ·ID指定输出数据集中作为ID的变量。 ·VAR语句中LEVEL关键字的可能取值如下:非对称型的名称变量 (ANOMINAL)、名称变量(NOMINAL)、顺序变量 (ORDINAL)、等距变量(INTERVAL)、比率变量(RATIO)。选 项可以是:ABSENT=、MISSING=、ORDER=、STD=、WEIGHTS= 等。 ·WEIGHT语句指定输入数据集中的某个数值变量为观测的权重, 该语句在计算距离中不起作用,但是会在标准化变量过程中起作用。 需要注意的是,上述VAR语句中WEIGHTS=选项和语句“WEIGHT 变量;”的作用是不一样的。前者是一个选项,用来对变量进行加权; 后者用来对观测进行加权。此外,某种距离计算方法往往仅适用于特定 类型的变量。因此,在进行距离计算前,应该考虑所分析变量的类型。 有关变量类型的介绍参见第9章。 例13.1:数据集work.stock包含了10只股票价格,以及它们在2010年 至2013年间的投资收益率(每百元)。现要对这10只股票进行聚类,以 分析每一类股票的特征。 首先根据数据集work.stock中的信息计算出每两只股票间的距离, 然后将其结果作为输入参数之一进行聚类分析。以下代码仅实现了距离 计算,有关利用距离作为输入进行聚类分析的部分见本章后面的内容。 data work.stock; title'Stock Dividends'; length Stock $3.; input Stock $ div_2010 div_2011 div_2012 div_2013; datalines; S1 8.4 8.2 8.4 8.1 S2 7.9 8.9 10.4 8.9 S3 9.7 10.7 11.4 7.8 S4 6.5 7.2 7.3 7.7 S5 6.5 6.9 7.0 7.2 S6 5.9 6.4 6.9 7.4 S7 7.1 7.5 8.4 7.8 S8 6.7 6.9 7.0 7.0 S9 6.7 7.3 7.8 7.9 S10 5.6 6.1 7.2 7.0 ; proc distance data=work.stock method=DCORR out=work.distdcorr; var interval(div_2010 div_2011 div_2012 div_2013); id Stock; run; 输出数据集work.distdcorr如图13.1所示。从该数据集可以读出每两 条观测之间的距离,例如观测S2与S1之间的距离为0.930192。 图13.1 数据集work.stock中观测之间的距离 13.2 划分法与层次法 前面介绍并比较了划分法与层次法之间的优劣,事实上,具体到算 法时,无论是划分法还是层次法都有很多不同的算法,并且伴随着人们 对聚类分析研究的不断深入,又不断有新的聚类方法出现。本节介绍划 分与层次法中最常见的方法,以及如何利用SAS相应的过程步来实现这 些方法。 13.2.1 使用过程FASTCLUS实现K均值聚类法 划分法中最常用的是K均值聚类法。K均值聚类法的一个最重要特 点是算法收敛的时间是和待分析数据的观测数成正比的。正因为如此, K均值聚类法可以用来处理规模较大的数据。 K均值聚类方法的大体步骤如下: 1)选定K个观测作为K类的种子(Cluster Seed)。 2)读入所有观测,计算每个观测与K个种子间的距离,并将观测 暂时归类到与其距离最近的种子所在的类中。 3)根据现有类中的观测,重新计算类的中心,即种子。 4)重复第2~3步,直至收敛。至此,所有K类的种子最终确定。 5)再次读入所有观测,将每个观测归类到与其距离最近的种子所 在的类,分类结束。 这里K值是预估的。对K值的估计方法有很多,常见的方法如下: ·根据背景知识判断,比如,知道数据中最多只可能有5类,那么K 值就可以设置为5。 ·根据分类目标判断,如银行对客户进行营销时,希望客户被分成 3~4类,那么K值就可以取为3或者4。 ·作图法,根据图像判断出大概的类数。 ·启发式法,先确定一个比较大的K值,采用某个聚类方法根据这个 K值对数据进行聚类分析,得出一个建议类数。然后根据这个建议类 数,再进行聚类分析直至该K值稳定在某一常数为止。 虽然K均值聚类方法的计算几乎集中在第2~4步,但是第1步对种子 的选择却是至关重要的,种子的选择从根本上决定了分类的好坏。 SAS通过PROC FASTCLUS来实现K均值聚类法。默认情况下, PROC FASTCLUS使用欧式距离来计算观测之间的距离。过程 FASTCLUS对种子的选择可以由用户指定,也可以由过程步在原始输入 数据集中选择。一般来说,过程FASTCLUS可以自动选出“足够好”的初 始种子。此外,PROC FASTCLUS具有以下特点: ·适用于分析较大的数据集,一般来说,观测数在100条以上。当数 据集较小时,过程FASTCLUS对数据集中的观测顺序较为敏感,即同一 数据集观测顺序的变化往往会影响产生的结果。 ·在过程FASTCLUS使用的算法中,方差较大的变量对分析结果的 作用也比较大,因此,在实际应用中,如果数据集变量方差之间差异较 大,可以考虑先对数据集进行标准化。 PROC FASTCLUS的一般形式如下: 其中: PROC FASTCLUS DATA =数据集 <SEED=> | <MAXC = > | <RADIUS = >| <MAXITER = >|<OUT =>; VAR变量; RUN; ·选项SEED=指定作为类种子的数据集,该数据集的变量必须和原 始输入数据集的变量相同。默认情况下,过程步使用前几条完整的数据 作为类种子。 ·选项MAXC=和选项RADIUS=这二者必须指定一个。其中,MAXC 值是用户允许聚类分析生成的分类数目的最大值,默认值为100; RADIUS值是更新聚类种子的阈值。若种子与前一个种子的距离大于该 阈值,则替换种子。该阈值的默认值为0。 ·选项MAXITER=为重新计算种子类的最大迭代次数。 ·选项OUT=指定输出数据集,该数据集不仅包含原数据集中的变 量,还包含新生成的变量CLUSTER以及Distance to Cluster Seed,前一 个变量表示观测所属的类,后一个变量表示当前观测与该类中心的距 离。 ·VAR语句指定用来进行聚类分析的变量,可以为一个或多个,变 量必须为数值型。 例13.2:数据集ex.Food_cal包含102种食物以及每种食物的成分,如 图13.2所示。共包含如下变量:Food(食物名称)、measure(测量单 位)、weight(重量)、kcal(卡路里)、fat(脂肪)、carbo(碳水化 合物含量)、protein(蛋白质)。现在要根据食物的卡路里、脂肪以及 蛋白质,采用K均值聚类法对数据集ex.Food_cal进行聚类分析。 图13.2 数据集ex.Food_cal 示例代码如下: proc fastclus data=ex.food_cal maxc=5 maxiter=10 out=work.clus; var kcal fat protein; run; 下面将具体分析输出结果。首先,输出结果展示的是初始种子的信 息图,如图13.3所示。从该图中可以看出:聚类1的中心为观测 (10.00000,0.000000,0.000000)。可以此类推其他聚类的中心。 在如图13.4所示的聚类汇总信息图中,可以看到每一类的具体信 息。例如,聚类1中共含有36个观测,RMS(Root Mean Square,均值平 方根)的标准差为1.5856,从该聚类种子到其余观测的最大距离为 4.4002,距离聚类1最近的类是聚类2,二者之间的距离为聚类质心间的 距离,即为148.5。 接下来的“变量的统计量”图则给出了一系列统计量,如图13.5所 示。 此外,PROC FASTCLUS还给出聚类内部每个变量的均值和标准 差,如图13.6所示。 图13.3 聚类的初始种子信息 图13.4 图13.5 聚类汇总信息图 变量的统计信息图 图13.6 聚类内部变量的均值与方差 最后,输出的数据集work.clus如图13.7所示。从该数据集中,可以 看出每一条观测所属的具体的类,以及该观测到该类中心的距离。例 如,第1条观测属于第1个类,该观测到第1类中心的距离为 2.1280388584。 图13.7 13.2.2 输出数据集work.clus 使用过程CLUSTER实现层次法 SAS共提供了11种层次法,这些方法可以通过指定PROC CLUSTER 中的选项来实现。过程CLUSTER的一般形式为: PROC CLUSTER DATA= METHOD= <选项>; VAR 变量名 ; RMSSTD 变量名; RUN; 其中: ·DATA=指定输入数据集,默认值为最后一次使用过的数据。 ·METHOD=用来指定做层次分析的具体方法,可供选择的方法共有 11种,相应的SAS关键字和对应方法如表13.2所示。该语句常见的选项 有:CCC用于输出CCC(Cubic Clustering Criterion)值,该值也称三次 聚类准则或者立方聚类条件;PSEUDO用于输出伪F统计量与伪T统计 量;RSQUARE用于输出统计量R方与半偏R方。 表13.2 PROC CLUSTER语句的选项 ·VAR是用户指定用来做聚类分析的数值变量名。 ·RMSSTD指定了方根标准方差变量。 在PROC CLUSTER中,使用不同的方法其聚类结果也会不一样。 下面简要地总结上述方法的优缺点。 一般情况下,在蒙特卡罗随机模拟方法中,分类效果比较好的 是“可变法”,分类效果比较差的是“MCQUITTY法”和“最短距离法”。对 离群点(Outlier,指的是少数几个点距离当前所有的群都比较远)比 较“敏感”的方法是“离差平方和法”和“最长距离法”,对离群点比较不敏 感的是“类平均法”、“重心法”。此外,上述11种方法中,除了“最大似 然法”外,其他方法均支持距离(数据集)作为过程步的输入。 例13.3:数据集ex.nutrition,包含了各种食物以及它们的营养成 分,如图13.8所示。其中共包含5个变量:Food(食物)、 percent_water(含水的百分比)、Protein_g(蛋白质含量)、 Saturate_Fat_g(饱和脂肪含量)以及Magnesium_mg(镁含量)。现在 要使用层次法对数据集ex.nutrition进行聚类分析。 示例代码如下: proc cluster data=ex.nutrition outtree=work.tree method=ave ccc pseudo; var Magnesium_mg percent_water Protein_g Saturate_Fat_g; id food; run; 下面来解释PROC CLUSTER生成的各个图表的含义。首先,PROC CLUSTER给出了类平均聚类分析图,如图13.9所示。该图包含特征值, 以及累积解释变异的比例。 图13.8 数据集ex.nutrition 图13.9 类平均聚类分析图 接下来PROC CLUSTER展示的是聚类历史图(这里只截取最后15 个类的合并过程),如图13.10所示。 图13.10 聚类历史图 其中,第1列显示当前的聚类数,在最开始所有的观测都各自为一 类,到了最后一步,所有的观测都会归为一类。第2列和第3列表示的是 当前合并的聚类。第4列频数指的是当前类包含的观测数。第5~6列为半 偏R方与R方,这两个统计量是用来帮助确定分类个数的:R方越大表示 类之间区分得越开,聚类效果越好;另一方面,不能以R方的大小简单 地确定分类个数,而应考察其值的变化,即上一步与该步R方值的差 异,这就是半偏R方,若半偏R方较大,说明本次并类的效果不好,应 当考虑聚类到上一步停止。第7列为R方期望的近似值。第8~10列为类 数判定标准: ·第8列显示的是CCC值,CCC的峰值表示建议聚类数。 ·第9列显示的是伪F统计量,该统计量描述的是在当前情况下(按 当前类数聚类),类与类之间的分离程度。因此,该值越大分类效果越 好。 ·第10列为伪t方,该统计量衡量的是当前合并的两个类之间的分离 程度,该值越小说明当前合并的两个类越合理,反之,则说明该步的聚 类效果不好,应当考虑聚类到上一步是否就应该停止。 上述3个统计量中,CCC值由代码中的关键字ccc生成,后两个统计 量由代码中的关键字PSEUDO生成,仅对使用AVERAGE、 CENTRIOD、WARD方法的坐标数据有效。此外,上述3个统计量建议 的类数可能会不一致,在这种情况下,建议综合上述3种统计量并辅助 其他统计量,如R方以及半偏R方进行考虑。 第11列为标准化的均方根距离(Normalized RMS Distance),该值 由类间距离除以观测间距离均方根得到,可以用来帮助判断聚类的合适 数量,当某一步的标准化均方根距离增加的幅度最大时,此步骤前的聚 类数最合适。 图13.11 根据CCC值、伪F值以及伪T方值分别生成的聚类数准则图 图13.11为聚类数准则图,该图的横坐标表示类数,共包含3个子 图,分别如下: ·三次聚类准则图,从该图可以看出,类数为3的时候CCC值达到峰 值。 ·伪F图,从该图中可以看出,当类数为3时,该统计量最大(类与 类之间的区分效果最好)。 ·伪T方图,当类数为3时,该值最小,当将类别数合并为2时,该值 有较大上升,说明合并不合理。因此,可以考虑在类数为3时停止合 并。 综合上面的分析,可以判断出PROC CLUSTER的建议类数为3。 代码中的关键字OUTTREE=WORK.TREE生成了用于生成树形图的 数据集work.tree。PROC CLUSTER自动调用PROC TREE生成树形图, 该图如图13.12所示。 图13.12 PROC TREE生成的树形图 从该树形图中可以看出每一类具体包含的观测,例如,Pinto Beans cooked from dry和Blackeyed peas frozen drained组成了一类。 至此,即完成了对整个代码输出结果的解释。 现在来了解过程CLUSTER。事实上,它的输入不仅可以是原始数 据集,也可以是由其他方法计算得出的距离数据集。例如,在例13.1 中,我们利用PROC DISTANCE计算出了观测间的距离,下面的例子完 成对上述10只股票的聚类分析,并将结果以树形图的形式展示出来。从 上面的例子可以看到,如果过程CLUSTER包含选项OUTTREE=,系统 就会自动调用过程TREE生成树形图。除了系统自动调用外,也可以利 用过程CLUSTER的输出直接调用PROC TREE。直接调用PROC TREE允 许我们更加灵活地控制树形输出,例如,通过PROC TREE,可以指定 横坐标对应的变量与变量的取值范围、间隔长度等。 PROC TREE的一般形式如下: PROC TREE <选项>; NAME变量; HEIGHT变量; COPY 变量 ID变量; RUN: 其中, ·PROC TREE语句中常见的选项有:DATA=用于指定输入数据集, HAXIS=用于自定义横坐标轴(可以结合系统选项AXIS使用,具体见下 例),HORIZONTAL用于指定生成水平树。 ·NAME语句用于指定数据集中树的节点的变量,如果该语句缺 失,PROC TREE会使用输入数据集中的_NAME_变量。 ·HEIGHT语句用于指定树形图中每个节点的高度,如果该语句缺 失,PROC TREE会使用输入数据集中的_HEIGHT或者_NCL_。 ·COPY语句用于指定从输入数据集复制到输出数据集的变量。 ·ID语句用于指定输出数据集中作为ID的变量。 例13.9:将例13.1中的计算结果,即数据集work.distdcorr作为PROC CLUSTERS的输入进行计算,然后将CLUSTER输入的树形数据集作为 过程TREE的输入,最终结果以树形图的形式展示。 示例代码如下: proc cluster data=work.distdcorr method=Ward outtree=work.Tree ; id Stock; run; axis1 order=(0 to 1 by 0.1); proc tree data=work.Tree haxis=axis1 horizontal; height _rsq_; id Stock; run; 代码中有关PROC CLUSTER的输出结果包含两部分:一部分是聚 类历史图,如图13.13所示。另一部分是聚类分析树形图,如图13.14所 示。 图13.13 聚类历史图 图13.14 聚类分析树形图 生成的数据集work.tree如图13.15所示。 图13.15 数据集work.tree 上述代码中的全局语句“axis1order=(0to 1by 0.1);”定义了一条坐 标轴的属性。在PROC TREE中,选项haxis=axis1使用该坐标轴,关键字 horizontal指定了输出水平树形图。语句“height_rsq_;”指定了输入数据 集中的变量_rsq_作为横轴(该变量的标签为R方),ID为变量stock。 PROC TREE输出的树形图如图13.16所示。 图13.16 PROC TREE输出的树形图 从图13.16可以看出,如果要把股票分成3类,那么第一类为S1和 S3,第二类为S2和S7,其余的为第三类。 13.3 本章小结 聚类分析在很多领域都有应用,聚类分析也可以作为其他算法的预 处理:先对数据集进行聚类,然后对每一类进行分析。聚类分析的一个 最重要的特征就是在分析前通常不知道应该将数据分为多少个类别才合 适。因此,在实际的处理过程中,可以尝试不同类别数的效果,也可以 采用不同的方法尝试进行分析。不管采用哪种方法,都应该结合分析问 题的背景具体分析。在聚类分析前,也可以尝试对待分析的数据集进行 数据探索,例如当变量个数较少时可以考虑对数据集作图(离散图), 这样可以对要分析的数据集有更深刻的了解,以便采用合适的方法分 析。 第14章 判别分析 从上一章内容可知,在进行聚类分析之前,所考察的对象可以分为 哪些类别是未知的。在实际应用中,我们也经常遇到考察对象分类结果 是已知的情况,例如,某商业银行根据信用卡等级评分模型将其用户划 分为3类:信用等级高、信用等级中以及信用等级低。由于在分类前, 我们已经知道分类结果,因此它不再属于聚类分析的范畴,而是属于判 别分析的范畴。判别分析是用来处理在已知分类结果的情况下对新数据 进行归类。 14.1 判别分析概述 本节主要介绍判别分析的理论知识,包括判别分析的基本思想以及 常见的判别分析方法。 14.1.1 判别分析的基本概念及应用 从统计的角度来看,判别分析可以描述为:已知有k个总体G1, G2,…,Gk,现有样本y,要根据这k个总体和当前样本的特征,判定该 样本y属于哪一个总体。其主要工作是根据对已知总体的理解,建立判 别规则(又称判别函数),然后根据该判别规则对新的样本属于哪个总 体做出判断。 判别分析在现实中有着广泛的应用。例如,在金融业,根据客户的 信息对其信用等级的分类;在人力部门,根据已有的员工类别及特征对 求职者进行相应的分类;在医学上,根据临床特征对是否染上某种疾病 做出诊断;在市场营销上,根据调查资料来判断下个时间段(月、季度 或年)产品是滞销、平常还是畅销;在环境科学上,根据大气中各种颗 粒的指标来判断地区是严重污染、中度污染还是无污染;在体育运动科 学中,根据运动员的各项生理指标及运动指标判断运动员适合短跑项目 还是长跑项目,等等。 判别分析是一种多元统计方法。在判别分析中,往往需要研究考查 对象的多个指标或变量,也就是说要有多个判别变量,才能建立合理的 判别规则,即判别函数。例如在上面信用卡的例子中,要正确地判定信 用等级,往往需要研究持卡人的职业、年龄、收入、交易历史等多个信 息。 14.1.2 判别分析的假设条件 进行判别分析会涉及下述假设条件,但并不是说,这些假设条件不 满足,就不能进行判别分析了,而是要根据这些假设条件来选择合适的 分析方法,或者是通过这些假设条件来了解它们对判别函数或判别效果 产生的影响。 ·每一个判别变量都不能是其他判别变量的线性组合。当一个判别 变量与另外一个判别变量高度相关时,虽然能求解,但是误差将会很 大。 ·各判别变量之间具有多元正态分布,即每个变量对于其他变量的 固定值有正态分布。当多元正态分布假设满足时,可以使用参数方法, 反之,则可以使用非参数方法,例如本章后面提到的核方法和近邻法。 ·在多元正态假设条件满足的前提下,使用参数法可以计算出判别 函数。更进一步,如果已知类别里变量的协方差矩阵相等,那么判别函 数为一次函数;反之,判别函数为二次函数。 14.1.3 判别分析常见的方法 判别分析常见的方法有距离判别、Bayes判别法和Fisher判别法等。 理解上述几种判别分析方法的思想原理对于正确设定判别分析过程步中 的选项是很有帮助的。 1.距离判别 距离判别是最简单、也是最基本的判别方法,其基本思想是根据样 本和不同总体的距离判定该样品所属的类别。样本和总体的距离由距离 函数来度量。例如,现有生产同一产品的两台设备A和B,设备A生产出 的产品的平均寿命μA=8(年),方差 =0.25,设备B生产出来的产品的 平均寿命μB=7.5(年),方差 现有一产品X,经测试得到该产品的 寿命为7.8(年),欲判断该产品是设备A生产的还是设备B生产的。从 直观上看,X与μA之间的距离较X与μB之间的距离小。但是,设备B生 产出来的产品寿命的稳定性差: 从另外一个角度上说,由设备B生 产的产品寿命的分散性更高,分散性越高意味着产品的寿命离平均距离 越远,从这个角度上看,X更有可能是由设备B生产出来的。为了将距 离和离散性结合起来考虑,定义距离函数如下: 由此,可以计算出X与设备A的距离 同理,X与设备B的距 离 进一步,可以定义判别函数 判别规则如下: ·如果W(X)<0,那么X与B的距离较近,进而认为X是由设备B生 产的; ·如果W(X)≥0,那么认为X是设备A生产的。 根据上述规则,由于W(X)<0,因此可以判定X属于B类设备生产 的产品。 这种将距离和分散性结合起来考虑的方法也称马氏距离 (Mahalanobis Distance),该距离由印度数学家Mahalanobis于1936年基 于协方差矩阵提出。假设总体G=(G1,G2,…,Gk)为m维(考察的 类别有k个,对应k个类别的子总体为Gk,指标有m个),均值向量 μ=(μ1,μ2,…,μm)',协方差矩阵为Σ,那么样本X=(X1,X2, …,Xm)′与总体G的马氏距离定义为 d2(X,G)=(X-μ)'Σ-1(X-μ) 马氏距离是最常见的距离函数之一,也是SAS用于判别分析过程步 的选项之一。根据马氏距离定义的判别函数W(X)可以为线性函数或 二次函数: ·当G1,G2,…,Gk的协方差矩阵相等时,W(X)为线性函数。 ·当G1,G2,…,Gk的协方差矩阵不全相等时,W(X)为二次函 数。 2.Bayes判别法 距离判别法简单、实用,但是该方法也有以下缺点: ·未考虑各个总体的分布,以及样本出现在各个总体G1,G2,…, Gk的概率(该概率在判别分析前出现,也称先验概率)。 ·没有考虑错判造成的损失。 Bayes判别法正是为了解决上述缺点而提出的。它将Bayes统计思想 用在了判别分析上:假设已知样本出现在各个总体Gi的概率即先验概率 P(Gi),在此基础上根据样本的信息,确定所观察到的样本属于各个 总体Gi的概率(即后验概率)。该判别法根据后验概率对样本进行归 类。 以下两种情形计算出来的判别函数是一致的: ·在Bayes判别法中,考虑错判造成的损失均等。 ·马氏判别法中,考虑先验概率以及协方差矩阵不全相等。 因此,Bayes判别法中的距离函数可以看作是马氏距离的推广。在 SAS判别分析的过程步的输出结果中,又将上述推广的马氏距离称为广 义平方距离。 使用Bayes方法需要提前输入先验概率。常见的先验概率有以下几 种: ·等概率 ·概率与样本容量成比例 ·根据历史资料与经验进行估计 下文会在介绍SAS判别分析的过程步时,逐一介绍上述几种概率的 设置方法。 3.Fisher判别法 Fisher判别法的基本思想是投影,具体地说,就是将k维数据投影到 某一个方向,使得投影后类间的差异最大。在判定类间差异的问题上, Fisher借鉴了一元方差的基本思想。由于Fisher判别法对总体的分布没有 任何要求,因此该判别法也被称为典型判别法。 为了更好地理解Fisher判别法,现在考虑两类总体的判别,如图 14.1所示。训练样本有两个类别,若将其沿着坐标的X或Y方向投影,它 们之间的区别都不是很显著(在实际计算中,可以通过不同类别间均值 差异来衡量区分效果)。可是如果将坐标旋转至X'Y'方向,将样本沿着 X'方向投影于Y'轴上,可以看出,投影后类别间的区分效果就比较好 了。 图14.1 两类总体的Fisher判别 把上述过程推广到多维的情形。在多维的情形下,好的投影是使得 变换(即几何上的旋转坐标轴)后同类别的点尽可能在一起,不同类别 的样本点尽可能的分离。Fisher判别法的实质是寻找一个最能反映类与 类之间差异的投影方向,即线性判别函数。一个好的线性判别函数 U(X)=C'X,在k个总体G1,G2,…,Gk中去求均值,所得到的k个 值,应当有较大的离差,这就是Fisher准则。这里,离差的意义如下: 其中,X为来自k个总体的样本(即来自这些总体的一维或多维随 机向量);Ei和Di表示在第i个总体中的均值和方差。 当确定线性判别函数U(X)之后,计算U(X)到各个总体的距 离,判定X为来自距离最小的那个总体。 当训练样本数据集中的类数太多时,一个判别函数可能不能很好地 区分类别,这时候就需要寻找第二个甚至更多的判别函数。一般认为, 当前所有的判别函数的效率达到85%以上即可。 14.2 判别分析在SAS中的实现 在SAS中,判别分析法可以通过以下3个过程实现:DISCRIM、 CANDISC及STEPDISC。 PROC DISCRIM既可以用来处理各个总体内也就是各个已知类别内 的数据服从多元正态分布的情况,也可以用来处理数据不满足多元正态 分布的情况。对于前者,PROC DISCRIM可以计算出一次或者二次判别 函数(取决于组间方差是否相等),并根据计算出的判别函数进行判别 分析;对于后者或者是在对各个类别里的数据分布没有特定假设的情况 下,PROC DISCRIM可以采用非参数方法进行判别分析。 PROC CANDISC是SAS中专门用来进行典型判别分析的过程。该过 程基于分析的数值变量,计算出最能描述类别间差异的典型变量。在使 用时,PROC CANDISC一般仅给出典型变量和得分数据,要获得完整 的判别分析的结果,需要将PROC CANDISC的输出结果作为PROC DISCRIM的输入进行进一步的分析。 PROC STEPDISC用于进行逐步判别分析,它采用向前选择、向后 移除或逐步选择的方法找出能够最有效地体现不同类别间区别的一个或 多个变量。 14.2.1 使用过程DISCRIM实现一般判别分析 前文提到的距离判别法和Bayes判别法均可以通过PROC DISCRIM 实现。PROC DISCRIM的一般形式如下: PROC DISCRIM<选项> ; CLASS变量; BY变量; FREQ变量; ID变量; PRIORS概率 ; TESTCLASS变量; TESTFREQ变量; TESTID变量; VAR变量; WEIGHT变量; RUN; 其中: ·有关PROC DISCRIM语句的选项如表14.1所示。 ·CALSS语句指定分类变量,变量类型可以是数值型也可以是字符 型。分类变量的不同取值定义了判别分析的不同类别。CALSS语句在 DISCRIM过程步中是必需的。 ·BY语句指定分组变量。PROC DISCRIM依据分组变量对每一组观 测逐一进行判别分析。使用该语句的前提是数据集已经按照分组变量排 序。 ·PRIORS语句指定样本出现在由分类变量(即CLASS指定的变量) 定义的不同类别内的先验概率,先验概率的值既可以是等概率,也可以 是各组样本在数据中出现的比例,还可以是用户指定的某些值。 ·上述3种取值中,“等概率”以及“各组样本在数据中出现的比 例”二者可以分别通过关键字EQUAL和PROPORTIONAL来设定。 ·对于用户指定的概率,可以使用“水平值=概率值”加以设定。例 如,假设分类变量为Grade,该变量的水平值为A、B、C和D,那么, 用户自定义的先验概率值指定如下:“PRIORS A=0.1B=0.3C=0.5D=0.1;”。如果水平值为小写字母或者带有前空格, 那么定义时水平值要加引号。例如,假设分组变量水平值字母为a、b、 c、d,那么可以按照如下格式定 义:“PRIORS'a'=0.1'b'=0.3'c'=0.5'd'=0.1;”。此外,水平值的格式必须严 格遵循CLASS变量中的格式,例如,假设C的值为5,格式为4.2,那么 PRIORS应该使用“5.00”而不是“5”或者“5.0”。 ·默认情况下,PRIORS的先验概率值为等概率。如果所有水平的 概率值之和不等于1,PRIORS按照当前概率值组成的比例对各个概率值 进行缩放。 ·VAR语句指定用于分析的数值变量。假如该语句缺失,那么过程 步将对其他语句未指定的所有数值变量进行分析。 ·ID语句指定输出数据集中用来做ID的变量。ID语句只有在PROC DISCRIM语句中指定LIST或者LISTERR选项时才有效。当DISCRIM过 程展现分类结果时,每条观测的ID变量值会被列出(默认是观测号被显 示)。 表14.1 PROC DISCRIM常见的选项 例14.1:数据集ex.cars_types和数据集ex.cars_test均是数据集 sashelp.cars的子数据集(如图14.2所示)。在变量多元正态的假设下, 现在要利用数据集ex.cars_types对数据集ex.cars_test进行判别分析(数据 集ex.cars_test的分类是已知的,这仅仅是为了检验判别分析的效果,需 要指出的是,下面的代码适用于类别未知的情况)。 图14.2 数据集ex.Cars_types和ex.Cars_test 示例代码如下: data ex.cars_types ex.cars_test; Set sashelp.cars; by make type; where type in ("SUV", "Sedan", "Sports"); if first.type then do; if Origin in ("USA" ,"Europe") then output ex.cars_types; else output ex.cars_test; end; run; proc discrim data = ex.cars_types testdata = ex.cars_test method = normal pool = test distance list testout = ex.car_results; class type; var Weight Wheelbase Length MPG_City EngineSize; run; 上述代码中的DATA步是用来生成训练数据集与测试数据集的。下 面,具体分析PROC DISCRIM部分代码。 在PROC DISCRIM中,进行判别分析的原始数据输入方式共有两 种: 第一种方式是将训练数据集和测试数据集分别存储为两个数据集, 这两个数据集中分类变量的变量名必须相同。这也是本例所采用的方 法。 第二种方式是将训练数据和测试数据放在同一个数据集中,并且用 同一个分类变量来标注各个样本所属的类别,例如本例中的Type变量; 而数据集中需要判定类别的观测所对应的分类变量之值在判定之前为缺 失值。 下面具体分析PROC DISCRIM的输出结果。首先来看输出的数据集 的一些基本统计信息,具体如图14.3所示。由于未指定先验概率,系统 默认将数据集ex.cars_types中观测归入SUV、Sedan、Sports三个不同类 的先验概率是一样的,即1/3。图14.3中最后一个表显示的协方差矩阵的 秩(矩阵的秩等于该矩阵非零的特征值个数)与矩阵行列式的自然对数 值,后者用于判别函数的构造。 图14.3 PROC DISCRIM输出的汇总信息 代码中的关键字POOL=TEST使用检验来判断不同Type的协方差矩 阵是否相同,进而决定是否合并对应不同Type的协方差矩阵。该关键字 的输出结果如图14.4所示。 如图14.4所示,由于卡方值在0.1水平处显著,因此可以认为方差的 齐性不满足。因而,判别函数为二次,且判别函数使用的是类间的协方 差矩阵。 代码中的关键字DISTANCE用于输出不同类间(即Type)的距离, 具体如图14.5所示。其中第一个表格显示的是平方距离信息,例如,从 Type SUV到Type Sedan的平方距离为34.49641;第二个表格显示的是广 义平方距离信息,如从Type SUV到Type Sedan的广义平方距离为 51.17552。 距离矩阵可以用于判断类间区分是否明显,如果某两类间的距离太 小,说明原始数据集中的分类不合理。当然,距离矩阵最重要的作用来 是用来生成判别函数以及判别规则。 图14.4 PROC DISCRIM关键字POOL=TEST的输出 关键字LIST输出的是根据判别函数计算的后验概率,以及对原始数 据集中观测新的归类结果。如图14.6所示是LIST的部分输出结果,从结 果中可以看出,第一条观测在原始数据集中的Type为Sedan,使用判别 函数计算出该观测分别有0.0204、0.8372和0.1424的概率属于SUV、 Sedan和Sports。于是根据概率值的大小,过程DISCRIM判定该观测 Type为Sedan。注意,如果有误判出现,那么过程以“*”号标记,例如图 14.6中的第9条观测。 紧接着是判别结果的两个汇总表格,如图14.7所示。第一个表格中 展示的是有关观测数以及百分比的信息,该表格的每一个单元格都包含 了两行数字,上面的数字为观测数,下面的数字为百分比。从第一个表 格可以看出,原数据集共有18条观测Type值为SUV,根据过程步生成的 二次判别函数,有15条被正确归入SUV,有2条被误判为Sedan,还有1 条被误判为Sports。第二个表格统计每一类以及整体出错的概率。从第 二表格可以看出,整个Type为SUV的误判率为16.67%,总体误判率为 8.89%。 图14.5 图14.6 关键字DISTANCE的输出 关键字LIST的输出结果 最后输出的是对需要判别的数据集ex.cars_test的判别结果。如图 14.8所示,待判别的数据中共包含33个观测,总体的误判率为22.77%。 图14.7 对数据集ex.cars_types的判别结果 图14.8 数据集ex.cars_test的判别结果 代码中testout=ex.car_results将具体的判定结果以数据集的形式输 出,数据集中的变量_INTO_表示观测被归入的Type,变量SUV、Sedan 和Sports分别为观测在这3个类别上的得分,具体如图14.9所示。 至此,对代码的分析全部结束。 在本例中,假设了是满足多元正态性条件的,在实际中,可以通过 Q-Q图等工具判断正态性条件是否满足。当正态性不满足时,可以利用 非参数方法加以分析,这里不具体展开讲。 图14.9 14.2.2 数据集ex.Car_results判定结果输出 使用过程CANDISC实现典型判别分析 回顾本章开篇介绍的典型判别法,该判别法的最终目的是找出若干 个投影方向,使得数据沿这些方向投影后,类别间的差异最大。在SAS 中,典型判别法可以通过PROC CANDISC来实现。PROC CANDISC对 原数据集用来判别分析的数值变量进行了线性组合,得到了若干典型变 量(Canonical Variables),使得原数据集的不同类别在这些典型变量上 的区分最明显。PROC CANDISC计算典型变量的过程与在主成分分析 中计算主成分的思想一致:找出若干个原始变量的线性组合,这些线性 组合能解释绝大部分的组间差异。 PROC CANDISC能够帮助我们找出典型变量,但这不是判别分析 的最终目的(最终目的是归类)。为了达到分类的目的,可以将PROC CANDISC的输出结果输入其他判别分析过程步中加以分析。 PROC CANDISC的一般形式如下: PROC CANDISC的一般形式如下: PROC CANDISC<选项> ; CLASS变量 ; BY变量; FREQ变量; VAR变量; RUN; 其中: ·有关CANDISC语句的部分选项如表14.2所示。 表14.2 PROC CANDISC的选项及其含义 ·CALSS语句指定分类变量,分类变量用于建立判别公式,变量类 型可以是数值型也可以是字符型。 ·VAR语句指定用于分析的变量。假如该语句缺失,那么对过程步 中的所有数值变量进行分析。 例14.2:使用典型判别方法对例14.1中的数据集ex.Cars_test进行判 别分析。 整个分析过程大体如下:利用CANDISC过程进行判别分析,可以 得到若干个典型判别函数,以及数据集中的每条观测的计算得分。这些 得分将作为DISCRIM过程的输入对数据集ex.cars_test进行判别分析。由 于CANDISC过程输入的数据集只能有一个,因此,先将数据集 ex.cars_test中的Type设为缺失(待判定),再将该数据集和数据集 ex.cars_types合并为数据集ex.cars_all。在利用数据集ex.cars_all进行典型 判别分析时,Type为缺失值的观测不会被用来计算判别函数,但是,经 过其他观测生成的判别函数会计算这些观测的得分。具体代码如下: data ex.cars_test_notype; set ex.cars_test; Type = " "; run; proc sql; create table ex.cars_all as select * fromex.cars_types union select * fromex.cars_test_notype ; quit; proc candisc data = ex.cars_all out = ex.cars_all_results distance; class type; var Weight Wheelbase Length MPG_City EngineSize; run; PROC CANDISC首先输出的是汇总信息和距离信息,这部分信息 和PROC DISCRIM一样,这里就不重复展示了。接下来,PROC CANDISC输出的是对样本中各分类均值是否相等所进行的4种多元检验 的结果,如图14.10所示。从该表格中可以看出,无论是依据哪一种检 验,我们都可以认定样本中不同类之间的均值不一样。 图14.10 4种不同假设检验结果 紧接着是典型相关假设检验,如图14.11所示。该表格的前半部分 是典型相关值,中间部分是特征值,第一个典型相关变量能解释的比例 仅为54.03%,但前两个典型变量能解释样本的全部变异。累积解释变异 的比例值也是我们判断采用典型变量个数的依据,一般来说,累积解释 变异的比例需达到80%。表格的后半部分是当前典型变量和之后典型的 相关性检验。典型相关为0的意思是两组变量没有线性关系。 图14.11 典型相关假设检验 CANDISC过程一共给出了3种判别系数,如图14.12所示。其中第一 个表格显示的是 图14.12 三种判别系数 基于总样本(Total Sample)的标准化典型系数,第二个表格是基于合 并类内(Pooled Within Class)的标准化典型系数,最后一个表格是基 于原始形式(Raw)的典型系数。 最后,“输出”窗口还给出了Type的不同类观测在典型变量Can1和 Can2上的均值,如图14.13所示。 图14.13 典型变量TYPE在Can1和Can2上的均值 PROC CANDISC输出的数据集ex.cars_all_results如图14.14所示。 图14.14 数据集ex.cars_all_results 至此,代码的输出分析结束。 在例14.2中,我们使用典型判别法找出了2个典型变量Can1和 Can2,并计算出了数据集中每一条观测在典型变量上的相应得分。为了 得到完整的判别结果,可以利用PROC DISCRIM对数据集进行进一步的 分析ex.cars_all_results。 例14.3:根据例14.2中计算出的典型变量,利用PROC DISCRIM完 成对数据集ex.cars_all的判别分析。 示例代码如下: Proc discrim data = ex.cars_all_results method = normal pool=test out = ex.cars_results2 ; class type; var can1 can2; run; 上述代码的输出结果与例14.1的输出结果类似,这里不赘述。根据 典型变量Can1和Can2计算出的判别函数对数据集进行判别分析的整体 误判率为7.22%,如图14.15的左图所示;这较例14.1中的整体判别效果 略微有提高,如图14.15的右图所示。 图14.15 例14.2和例14.1的分析结果对比 最终的判别结果可以通过查看数据集ex.cars_results2中变量_INTO_ 的值来了解,如图14.16所示。 图14.16 数据集ex.cars_results2 将数据集ex.cars_results2和数据集ex.cars_test对比可以看出,数据集 ex.cars_results2中共有7条观测被误判,这个精度与例14.1一致(误判的 观测略有不同,但是总数均是7条)。 14.2.3 使用过程STEPDISC实现逐步判别分析 在前面介绍的判别分析中,给定分类变量和用来判别分类的数值型 变量后,我们将所有的数值型变量都一次性囊括在分析中了。但在实际 应用中,可能会有部分数值型变量的判别效果较差,在这种情况下,没 有必要将这些判别效果较差的变量囊括进判别分析。例如,数据中的不 同类别在某一变量上的均值差异不大,在这种情况下,使用该变量进行 分类的效果将不是很好。还有一类变量,独立考虑时的确能够很好地区 别数据中的不同类别,但是将这些变量囊括进判别分析的变量后,就有 可能显得多余了。对此,就需要移除数据中的判别效果不好的、多余的 变量。在SAS中,STEPDISC过程可以用来对判别分析变量进行甄选。 给定一个数据集和分类变量以及数值型变量后,STEPDISC过程可 以选择出其中的若干个(也可能是全部)数值变量用于判别分析。 STEPDISC过程选择变量的方式共有以下3种: ·向前选择(FORWARD SELECTION) ·向后移除(BACKWARD ELIMINATION) ·逐步选择(STEPWISE SELECTION) 所谓向前选择,指的是开始时模型中没有变量,然后逐步向模型中 添加变量,每步仅添加一个判别能力最强而且大于事前设置的阈值的变 量。衡量判别能力强弱的准则为WILKS'S LAMBDA值,该值越小,判 别能力越强。当不在模型中的变量都达不到事前设置的阈值时,向前选 择停止。 相反,向后移除会在开始时囊括所有的变量,再依据给定的 WILKS'S LAMBDA统计量的值,逐一移除模型中判别能力最小且判别 能力小于WILKS'S LAMBDA的变量。当剩余的所有变量都达到 WILKS'S LAMBDA相似比值准则的标准时,向后移除过程停止。 逐步选择可以看作是向前选择和向后移除的综合。开始使用该方法 时,模型中不包含任何变量,然后在模型中添加判别能力最强的变量, 随着模型中变量的逐渐增加,较早引入的变量的判别能力也可能随之变 化,如果模型中某变量的判别能力小于阈值了,就移除该变量。然后反 复这个过程,直至模型所包含的变量都满足WILKS'S LAMBDA相似比 值准则,且其他变量都达不到进入模型的标准为止。 由于逐步判别法每次仅选择一个变量,并没有考虑待筛选变量之间 的关系,因此,理论上说,某些关键的变量可能会被排除在最终的模型 外。此外,逐步判别是依据WILKS'S LAMBDA统计量值来衡量判别能 力的大小的,但该统计量也不总是衡量变量判别能力的最好准则,所以 不排除关键变量被排除在最终模型外的可能。但是即便如此, STEPDISC过程仍然可以作为DISCRIM过程和CANDISC过程的一个很好 的补充。 PROC STEPDISC的一般形式如下: PROC STEPDISC<选项>; BY 变量; FREQ 变量; VAR 变量; WEIGHT变量; RUN; 在上述语法中,BY语句、FREQ语句、VAR语句以及WEIGHT语句 的功能与PROC DISCRIM中对应语句的功能相同,这里仅对PROC STEPDISC语句中的选项加以介绍,如表14.3所示。 表14.3 PROC STEPDISC常见的选项 例14.4:使用过程STEPDISC对数据集ex.cars_all进行判别分析。 示例代码如下: Proc stepdisc method = stepwise data = ex.cars_all; class type; var Weight Wheelbase Length MPG_City EngineSize; run; 这里需要注意的是,在使用过程STEPDISC时,数据集ex.cars_all中 type为缺失值的部分(即需要判断的数据)不会被用来计算。下面分析 代码的输出。 过程STEPDISC首先输出的是基本统计信息,如图14.17所示。从输 出中也可以看出,type为缺失值的部分并没有用来分析(数据集 ex.cars_all共有82条观测,其中49条观测为非缺失值),输入变量与保 留变量的显著性水平均为默认值0.15。 接着,SAS输出结果给出变量筛选过程的每一步具体信息。在第1 步中,模型中没有任何变量,可供选择的变量为VAR语句中指定的变 量,共有5个。根据引入变量显著性水平阈值(该值为0.15),所有变 量均符合条件(表中对应为Pr>F列的值均小于阈值0.15)。过程 STEPDISC在所有符合要求的变量中,选出影响最大的变量,即F值最大 或者Pr>F值最小。因此,第1步输入的变量为Weight。具体如图14.18所 示。 图14.17 STEPDISC过程输出的基本统计信息 图14.18 PROC STEPDISC逐步选择(一) 经过第1步后,模型中现已经包含了1个变量。当模型中包含的变量 为非空时,引进新的变量会对之前已经存在于模型中的变量有影响。因 此,在引进下一个新的变量之前,过程STEPDISC会对模型中现有的变 量进行检验,根据事先设定的显著性水平阈值(本例中为系统默认值 0.15),移除不符合条件的变量。 如图14.19所示,在模型当前变量中,没有可以删除的变量。接 着,模型考虑引进新的变量,这次输入模型的变量为EngineSize。 图14.19 PROC STEPDISC逐步选择(二) 以此类推,本例中,过程STEPDISC共进行了5次变量的逐步选择 (第5步,没有可以删除的变量,也没有符合条件的输入变量),这里 不一一描述。最终,汇总结果如图14.20所示。 图14.20 逐步选择汇总结果 从上面的汇总结果中可以看出,最终模型中包含了4个变量,这4个 变量在判别分析中的重要性逐次递减。至此,对代码输出的分析结束。 对于依照逐步选择法筛选出来的变量,可以利用前面介绍的判别分 析方法建立判别函数(判别规则),然后对测试数据集加以分类。例 如,以下代码会使用过程DISCRIM对STEPDISC选择出来的变量加以分 析。 proc discrim data = ex.cars_types testdata = ex.cars_test method = normal pool = test distance list testout = ex.car_results; class type; var Weight EngineSize MPG_City Wheelbase; run; 运行程序后,输出结果与例14.1类似,读者可以自行分析结果。 14.3 本章小结 与前一章介绍的聚类分析不同,判别分析在进行分类前,已经对分 类有了一定的经验。这些经验可能包含在一个已经分好类的数据集中, 也称训练数据集。从这些数据集中可以“提炼”出判别规则,根据这些判 别规则,即可对新的、未类分的数据进行分类。 如果数据集符合(多元)正态性分布,那么可以采用参数法;反 之,则需要考虑采用非参数法了。无论是参数法还是非参数法都可以通 过过程DISCRIM实现。过程CANDISC和STEPDISC的作用是分别找出 (对判别分析)起作用的投影方向和变量。一般来说,经过二者处理后 得到的投影方向与变量个数都会少于原数据集中的变量个数,因此,从 这个意义上讲,二者也是变量降维的工具(Dimension-Reduction Technique)。经过过程步计算得到的投影或变量,可以供DISCRIM使 用。总之,我们要结合待分析数据集的特征来选择合适的判别分析法。 第15章 回归分析 变量之间的关系,一般可以分为两类,一类是函数关系,例如圆的 面积S与半径r之间的关系S=πr2,矩形的周长L与两条边a和b的关系 L=2a+2b,欧姆定律指出电压V、电流I和电阻R的关系是V=IR,等等。 这一类关系的特征是,一个变量随着其他变量的确定而确定。另一类关 系是相关关系,这一类关系的特征是,变量之间的关系很难用一种精确 的方法表示出来。例如,人的身高与体重之间有一定的关系,但是,由 身高不能精确地计算出体重,由体重也不能精确地求得身高。又如人的 年龄与血压之间的关系,农业上的施肥量与单位产量之间的关系,等 等。需要指出的是,函数关系与相关关系之间没有一道明确的分界线, 一方面,由于存在测量误差等原因,在实际问题中,函数关系往往通过 相关关系表现出来;另一方面,当对事物内部规律了解得更加深刻时, 相关关系可能会转化成确定性的函数关系。 在前面的章节中已经学习了用方差分析的方法分析离散型随机变量 和连续型随机变量之间的关系。本章将继续学习运用回归分析的方法处 理连续型随机变量之间的相关关系,主要讨论一个随机变量与影响它的 另外一个或几个随机变量之间的统计依赖关系。受其他变量影响的这个 随机变量,称为响应变量(Response Variable)或者因变量;其他的对 于响应变量有影响,或者说可以用来解释响应变量变化的变量,称为解 释变量(Predictor Variable)或者自变量。自变量可以是随机变量,也 可以是普通变量(有确定取值的变量)。在后面的讨论中,为了便于描 述,对于随机变量和普通变量,都统称为变量。 所谓回归分析,就是定量地研究因变量受自变量影响的大小,并通 过建立回归方程对因变量的取值进行预测或控制的统计方法。回归分析 是常用的一种数理统计方法,在工农业生产和科学研究各个领域中均有 广泛应用。回归分析一般分为线性回归分析与非线性回归分析。从统计 工程的角度出发,一般首先认为变量之间呈线性关系;而非线性关系在 确定时,需要有理论的支撑,单纯从数据出发对非线性关系进行判断存 在一定的风险。本章着重介绍线性回归分析,它是两类回归分析中较简 单的一类,也是被广泛应用的一类。 15.1 变量关系探索 在统计分析中,对两个连续型变量进行回归分析时,第一个非常重 要的步骤就是观察和描述两个连续型变量之间的关系,可以通过散点图 来直观地观察两个变量的关系。通过散点图,可以查看并了解两个连续 变量的取值是否有异常值、是否存在一定的趋势、取值的大致范围等特 征。如图15.1所示为用散点图表示两个变量之间关系的示例:左上图显 示两个变量之间基本呈线性关系,即随着X的上升,另一个变量也增 大;右上图显示两个变量之间呈现出二次函数关系;左下图显示两个变 量之间存在周期性关系;右下图中的散点基本上均匀分布在整个平面 上。可见,从散点图上基本判断不出两个变量之间的关系。 图15.1 15.1.1 两个连续变量之间的关系图 皮尔逊相关系数 如果两个变量之间呈现出线性关系,则称这两个变量是线性相关 的,否则两个变量不是线性相关的。若无特殊说明,后面在提到相关性 时,都表示线性相关。若一个变量的值随着另一个变量值的增大而增 大,则两变量正相关;若一个变量的值随着另一个变量值的增大而减 小,则两变量负相关。皮尔逊相关系数是最常见的用来描述变量线性相 关性的统计量,如图15.2所示。在前面的章节中,已经介绍了皮尔逊相 关系数的计算公式,这里将继续深入介绍皮尔逊相关系数的特征。 皮尔逊相关系数的取值介于-1和1之间;当皮尔逊相关系数的取值 接近于-1或者1时,表示两个变量之间具有强相关性;当取值接近于0 时,表示两个变量不是线性相关的;当取值大于0时,表示正相关;当 取值小于0时,表示负相关。有的时候,两个变量的相关系数很大,也 有可能是因为受其他变量的影响。因此,在解释相关性时,特别需要注 意强相关性并不一定代表着一个变量变化会导致另一个变量的变化;换 句话说,相关关系不是因果关系。 图15.2 皮尔逊相关系数 如图15.3所示,在这一组数据中,X的取值绝大多数都在0和4之 间,相应的Y的取值在0和2.5之间,但是有一个非正常数据分布在散点 图的右上角,通过该组数据计算得出的皮尔逊相关系数为0.8903,看上 去X和Y具有较强的线性相关性。事实上,绝大多数散点分布在图形的 左下角,基本呈水平直线,也就表示X和Y之间不存在较强的线性相关 性。如果去掉右上角的非正常数据,重新计算皮尔逊相关系数 为-0.096,接近于0。 图15.3 包含极值时的皮尔逊相关系数 通过这个简单的例子我们知道,非正常数据对皮尔逊相关系数的影 响很大。在实际分析中,如果遇到这样的非正常数据(也称为强影响 点),需要结合数据的背景进行取舍。如果该数据是正确的,继续查看 在该数据和其余正常数据之间是否存在由于样本选取的随机性而漏掉的 数据:在总体数据中可能存在很多类似的数值,但在进行抽样的过程中 该群体只被抽取到一个或几个值,这就使得其在样本中与其他样本数据 显得格格不入,成为了强影响点,操纵着整体相关系数的强弱。在进行 综合的考量后,再决定在判断相关性时是否应将该类数据包含进去。 注意 皮尔逊相关系数是用来描述线性相关的统计量,当皮尔逊 相关系数的取值接近于0时,表示两个变量不是线性相关的,但是并不 代表两个变量之间不存在其他的相关性,如周期性。 15.1.2 相关性检验 相关性检验是检验两变量是否存在相关关系的一种假设检验。在该 假设检验中,ρ是表示相关系数的参数,γ是通过样本计算出来的统计 量,γ是ρ的一个估计。相关性检验的原假设H0和备择假设H1分别为 H0:ρ=0 H1:ρ≠0 一般情况下,当p值小于0.05时,拒绝原假设H0表示我们有足够的 证据证明相关系数ρ是非0的,也就说,两个变量之间的线性关系是显著 的。p值的大小不能表示相关性的强弱,并且p值的大小会受到样本容量 的影响。因此,在进行相关性检验时,也要同时查看统计量γ的取值。 15.1.3 CORR过程 CORR过程是SAS中用来画散点图、计算相关系数及进行相关性检 验的过程步,CORR过程的基本语法如下: PROC CORR DATA=数据集选项; VAR分析变量; WITH变量; ID变量; RUN; 其中: ·VAR语句用来指定需要进行相关性分析的变量,可以有多个变 量。WITH语句中指定的变量可以称为WITH变量。当没有使用WITH语 句时,系统将自动进行VAR语句中每两个变量的相关性分析。当同时使 用VAR语句和WITH语句时,系统将分别分析VAR语句中的每个变量和 WITH语句中每个变量的相关性,并且VAR语句中指定的变量将作为相 关系数矩阵的列,WITH语句中指定的变量将作为相关系数矩阵的行。 ·ID语句用来指定ID变量,在作图时,通过ID变量可以方便地对数 据进行定位。 默认情况下,CORR过程将输出皮尔逊相关系数和对应的p值。 PROC CORR语句中的选项PLOTS=可以指定子选项,控制图形的输出, 基本语法如下: PLOTS<(ONLY)><=MATRIX<(MATRIX选项)><SCATTER<(SCATTER选项)>>>; 其中: ·使用子选项ONLY时,系统只输出选项PLOTS=指定的图形,不输 出CORR过程中其他默认的图形。 ·MATRIX<(MATRIX选项)>:指定画出散点图矩阵,即将多个 散点图通过矩阵的形式展现出来,每个矩阵的元素是一个散点图。 ·SCATTER<(SCATTER选项)>:指定作两两变量的散点图,皮 尔逊相关系数将显示在散点图中。 ·括号中可用的MATRIX选项包括以下几项。 ·HIST|HISTOGRAM:在散点图矩阵中,对角线上的元素显示变 量的直方图。 ·NVAR=ALL|n:当VAR语句中指定了多个分析变量时,NVAR= 用来指定需要分析的变量个数。默认情况下,NVAR=5。当 NVAR=ALL时,最多可以分析10个变量。该选项也可以作为SCATTER 选项。 例15.1:某大型服装连锁销售机构希望对各门店年收入情况进行研 究,分析哪些因素和各门店的年收入相关。数据集ex.retail中包含了各 门店的收入状况及相关数据,以下是数据集中各变量的名称和描述。 ·StoreID:各门店ID号。 ·Revenue:最近一年门店的年收入,以万元为单位。 ·Member:门店销售人员人数。 ·Square:门店展示面积。 ·Inventory:月均库存量,以万元为单位。 ·Loyalty:门店会员人数。 ·Population:门店日均客流量。 ·Tenure:门店从开业至今的时间,以月为单位。 示例代码如下: ods graphics /reset=all imagemap; title "Correlations and Scatter plots with Revenue"; proc corr data=ex.retailrank plots(only)=scatter(ellipse=none nvar=all) ; var Member Square Inventory Loyalty Population Tenure; with Revenue; ID StoreID; run; 上述程序中运用CORR过程步分析了变量Member、Square、 Inventory、Loyalty、Population和Tenure与Revenue的关系,并且作出了 散点图。在ODS GRAPHICS语句中使用选项IMAGEMAP可以使得在 HTML输出的散点图中,当鼠标放到某一个散点时,系统就会自动显示 观测的信息。在本例中,当鼠标放到某个散点时,系统会自动显示X轴 变量、Y轴变量(Revenue)、观测号及StoreID。 选项RANK使得输出报表中的皮尔逊相关系数按从大到小的顺序排 列。选项PLOTS使得系统作出散点图。 输出内容如图15.4所示。默认情况下,系统首先输出WITH变量和 分析变量的简单统计量,然后输出相关变量的皮尔逊相关系数,以及相 应的P值。 图15.4 例15.1中描述性统计量和皮尔逊相关系数报表 可以看到,Revenue和Square相关系数的估计是0.73147,P值 <0.0001,表明相关系数显著不为0,Revenue和Square是显著正相关的。 第二大的皮尔逊相关系数的估计是0.55098,它是Revenue和Population的 相关系数。 散点图如图15.5所示。 从相关系数和散点图来看,Square、Population和Revenue的相关性 较强,暗示了这两个变量对Revenue可能具有较好的预测能力。 图15.5 例15.1输出的散点图 图15.5 (续) 在上面的例子中,希望预测的是门店的年收入,这是回归分析的目 的,因此,在这里,Revenue是因变量,Square、Population和Tenure等 都是自变量。在前面的相关分析中,已经分析了各自变量与因变量之间 是否存在相关关系。现在,使用CORR过程来计算并输出变量两两之间 的相关系数。 例15.2:接着例15.1,计算各自变量之间的相关系数。 示例代码如下: ods graphics /reset=all ; title "Correlations and Scatter Plot Matrix of Revenue Predictors"; proc corr data=ex.retail nosimple plots=matrix(histogram nvar=all) ; var Member Square Inventory Loyalty Population Tenure; run; 程序中的选项NOSIMPLE指定不输出基本统计量报表。 输出内容如图15.6所示。 图15.6 例15.2输出两两变量的皮尔逊相关系数报表 上面的相关系数矩阵中输出了自变量的两两相关系数及P值, Loyalty和Member的相关系数为0.98532,Tenure和Member的相关系数为 0.98118,相应的p值都很小,说明它们之间是显著相关的。如图15.7所 示的散点图也从细节上说明了该情形。 图15.7 例15.2输出散点矩阵图 15.2 线性回归 在本章第一节中,运用相关分析定量分析了因变量和自变量的相关 性。然而,即使两组变量的相关系数相同,线性关系也可能是完全不一 样的,如图15.8所示。图中这两组数据的相关系数都为0.99,但是代表 的线性趋势却大不相同。换句话说,相关系数并不能量化因变量受自变 量影响的大小。在本节中,将介绍如何具体量化因变量和自变量之间的 线性关系,并建立基于自变量的回归方程来预测因变量的取值。在实际 应用中,回归方程通常是未知的,回归分析的任务是根据样本数据估计 回归方程,讨论有关回归系数的点估计、区间估计、假设检验等问题, 重要的是根据自变量x的取值对因变量y作出预测。 图15.8 15.2.1 两组数据的散点图 基本原理 在一元线性回归中,假设因变量y和自变量x具有的线性关系为 y=β0+β1x+ε,其中β0是截距,也就是当x=0时响应变量的取值;β1是斜 率,代表当自变量x变化一个单位时,因变量改变的量;ε是随机误差, 代表y与β0+β1x的差异。一般情况下,影响y的自变量往往不止一个,假 设有x1,x2,…,xk,k个解释变量,考虑如下的线性关系式: y=β0+β1x1+β2x2+…+βkxk+ε,βi(i=1,…,k) 该式表示当自变量xi变化一个单位,而其余自变量都保持不变时, 因变量改变的量。以一元线性回归为例,对于样本点(x1,y1), (x2,y2),…,(xn,yn),相应的随机误差εi=yi-β0+β1xi,i=1,…, n。线性回归对随机误差εi有如下假设条件,εi必须是独立同分布的,且 εi~N(0,σ2)。 以一元线性回归为例,要估计总体的参数(β0,β1),常用的最小 二乘估计法的基本思想是,通过样本计算出参数的估计值 使得预 测值与实际值之间的差异最小,即, 其中yj是因变量的实际值,xj是自变量的值,n是样本容量, 就是 总体参数(β0,β1)的最小二乘估计。可以证明,(β0,β1)的最小二 乘估计 是最优线性无偏估计(Best Linear Unbiased Estimators, BLUE),也就是极大似然估计。 为了便于后面的讲解,先来回顾一下几个统计学中的名词: ·全部平方和(Totalsum of squares,SST):指的是响应变量的方 差,等于∑(Yi-Y)2,也称为总变异性,其中,Yi是响应变量的观测 值,Y是响应变量的均值。 ·模型平方和(Model sum of squares,SSM):指的是回归模型和响 应变量均值的差异,等于 其中, 是响应变量的预测值。 ·误差平方和(Error sum of squares,SSE):指的是回归模型和实 际观测值之间的差异,等于 该部分是由其他未能控制的随机干 扰因素引起的。 可以证明, 即总变异性可以分解为等式右边的两 部分,第一部分可以通过回归模型刻画出来,也称为模型解释的变异性 (Explained Variability),第二部分不能通过回归模型体现出来,称为 模型未解释的变异性(Unexplained Variability)。用图形表示如图15.9 所示。 图15.9 变异性图示 ·R方(R Square),也记为 表示模型解释的变异占总变 异性的百分比,R方的取值在0和1之间,R方越大越好,其越大则表示 模型解释的变异的比重越大,模型拟合得越好,是评价模型拟合优度的 重要指标。 ·调整的R方(Adjusted R Square) n是样本容量,p是参数 个数。当回归方程存在截距时,i=1;否则,i=0。在进行多元线性回归 时,随着模型中变量个数的增加,R方也会增大,容易形成“自变量越多 模型拟合得越好”的错误判断。此时应参考调整的R方来判断模型的拟合 优度。 15.2.2 假设检验 1.线性模型的显著性检验 在上面的讨论中,假定y关于x的回归y(x)具体形式为: β0+β1x1+β2x2+…+βkxk+ε,在处理实际问题时,y是否与x1,x2,…,xk 具有线性关系,首先要根据有关专业知识和实践来判断,其次就要根据 实际样本数据运用假设检验的方法来判断。换句话说,求得的线性回归 方程是否具有实用价值,一般来说,需要经过假设检验才能确定。若线 性模型y=β0+β1x1+β2x2+…+βkxk+ε符合实际,则β1,β2,…,βk不应全为 0,倘若β1=β2=…=βk=0,则y就不依赖于x1,x2,…,xk了。我们希望有 充分的理由来证明线性模型是符合实际的,因此需要进行如下假设检 验: H0:β1=β2=…=βk=0, H1:β1,β2,…,βk不全为0 当H0成立时,模型退化为y=β0, 记为F,由方差 分析知,当H0成立时,即β1=β2=…=βk=0符合实际,误差主要由随机误 差产生,所以F的取值应较小。F分布的示意图如图15.10所示。 当给定显著性水平α后,F1-α就是F分布位于1-α处的分位数(如图 15.10所示),如果由样本计算出F的值大于F1-α,则说明在显著性水平α 下,有足够的证据证明原假设H0不成立,应拒绝H0,即认为y与x1, x2,…,xk显著地具有线性关系;否则,接受H0,认为y与x1,x2,…, xk之间的线性关系不显著。线性关系不显著的原因可能有如下几种: ·影响y取值的,除x1,x2,…,xk及随机误差外,还有其他不可忽 略的因素。 ·y与x1,x2,…,xk的关系不是线性的,而是存在着其他的关系。 ·y与x1,x2,…,xk不存在关系。 当线性关系不显著时,需要进一步分析原因,分别进行处理。 2.回归系数的显著性检验 如果经检验得知y与x1,x2,…,xk之间具有显著的线性关系,但是 每个自变量对因变量y的影响作用并不是一样重要的,可能有的起重要 作用,有的则可有可无。因此,在否定线性模型所有自变量的系数全部 为0之后,还需从线性模型中剔除那些可有可无的自变量,保留那些比 较重要的自变量,重新建立更为简单的线性回归方程,以便更有利于实 际应用。 图15.10 F分布-概率分布函数示意图 对某个回归系数进行显著性检验的原假设和备择假设分别如下: H0i:βi=0,i=0,1,2,…,k H1i:βi≠0 可通过构造T统计量或者F统计量,根据T分布或者F分布的理论来 对回归系数进行检验,在SAS中,REG过程应用T检验法可进行回归系 数的显著性检验,并且可输出T值和相应的P值。当P值很小时,说明应 拒绝原假设,对应的回归系数显著不为0。 3.残差检验 前面进行回归分析时,假设条件是残差εi是独立同分布的,且服从 N(0,σ2)。因此在确定了回归方程之后,要回过头来查看残差是否服 从假设条件。如果残差不服从假设条件,则需要重新考虑回归方程的形 式和参数估计。 下面来看几幅残差散点图,如图15.11所示。这些都是关于自变量 取值的残差图,以自变量值为横坐标,残差值为纵坐标。 根据图15.11可以看出以下几点: ·如果残差是独立同分布的,且服从N(0,σ2),那么各个点应该 随机地分布在残差散点图上,类似左上图,没有任何规律和模式。 ·右上图中,残差随着自变量的取值呈现出二次函数的形状,说明 回归方程没有将该自变量的某些规律刻画出来,可以尝试在回归方程中 添加该自变量的一个二次项。 ·左下图说明残差的方差不是齐性的,随着自变量的取值增大,方 差增大,这种情况下可以尝试将因变量做一个转换。或者尝试其他不需 要方差齐性假设的模型,可参考GENMOD过程或者GLIMMIX过程。 ·右下图说明残差不是独立的。在这幅图中,残差是自相关的,当 数据和时间相关时,比较容易出现该种情形。可以尝试使用AUTOREG 过程进行建模(后面章节中的时间序列分析会介绍如何使用AUTOREG 进行建模)。 当进行多元线性回归时,可以分别作出关于各自变量的残差图,判 断是否各自变量中的规律是否都已经被提取出来了。 图15.11 15.2.3 残差散点图 模型拟合 运用REG过程建立线性模型,基本语法如下: PROC REG DATA=数据集 选项; MODEL因变量=自变量1 <自变量2 …></选项>; RUN; QUIT; 其中: ·MODEL语句用来指定因变量和自变量,因变量和自变量必须都是 数值型变量,当只指定一个自变量时,建立一元线性回归方程;若指定 多个自变量,则建立多元线性回归方程。 ·使用REG过程时,在QUIT语句之前RUN语句之后,可以任意添加 适用于REG过程的语句,而不需要重新提交PROC REG语句,这种用法 也称之为RUN组用法。在SAS中,不少过程步都支持RUN组用法,如 GPLOT过程和GCHART过程等。 例15.3:从例15.1中,已经得知变量Revenue和Square之间显著线性 相关。本例将调用REG过程来分析它们之间存在何种线性关系,其中, Revenue是因变量,Square是自变量。 示例代码如下: title "Predicting Revenue from Square"; proc reg data=ex.retail; model Revenue=Square; run; quit; REG过程输出的第一个报表显示读取的观测数和使用的观测数一 样,如图15.12所示。表明变量Revenue和Square都没有缺失值。 第二个方差分析报表中输出了线性模型显著性检验的结果,如图 15.13所示。 图15.12 图15.13 例15.3输出数据集中观测信息 例15.3输出线性模型显著性检验报表 其中,“源”主要是指变异性的来源,源的取值有3个,分别为模 型、误差和校正合计,分别代表回归方程解释的变异性、回归模型未解 释的变异性及总变异性。在图15.13中,平方和这一列代表变异性的数 值,对应于模型、误差和校正合计3个源,其值分别为本章在前面介绍 的SSM、SSE及SST的取值。这里的F值=112.78,对应的P值<0.0001,因 此,根据本章“假设检验”章节中关于“线性模型的显著性检验”的介绍, 可以判断Revenue与Square具有显著的线性关系。 第三个报表输出了模型拟合优度信息,如图15.14所示。 R方的取值介于0和1之间,代表模型解释的变异性占总变异性的比 重。在这个示例中,R方=0.5351,表示回归模型能够解释响应变量54% 的变异。在一元线性回归中,R方的取值等于皮尔逊相关系数的平方。 第四个报表输出了回归模型的参数估计和假设检验结果,如图 15.15所示。 这里Intercept和Square的回归系数对应的T值为3.71和10.62,相应的 P值都小于0.05,根据15.2.2节“假设检验”中关于“回归系数的显著性检 验”的知识可知,Intercept和Square的系数都是显著不为0。 图15.14 图15.15 例15.3模型拟合优度信息 例15.3中回归模型的参数估计和假设检验报表 因为β0(即Intercept)和β1(即Square的回归系数)的估计为 31.47125和1.47825,所以回归方程为 Revenue=31.47125+1.47825×Square。该模型说明Square每增加一个单 位,Revenue将增加1.47825个单位。当然,在对模型进行解释的时候, 一定要注意自变量的取值范围。 接下来输出模型的拟合诊断信息和残差信息,如图15.16和图15.17 所示。这些图形可以帮助判断样本是否满足回归分析的假设条件,以及 是否包含强影响点(Influential Observations)。 图15.16 例15.3中回归模型的拟合诊断面板 图15.17 例15.3中回归模型的残差图 需要指出的是,拟合诊断面板中输出的是关于预测值的残差图(拟 合诊断:Revenue的左上角图),它以因变量的预测值为横坐标,对应 的残差值为纵坐标,该图可以从总体上检验残差是否满足回归分析的假 设条件。该图与以自变量取值为横坐标、残差值为纵坐标的残差图有所 区别,对于自变量的残差图,可以按自变量检查残差的分布,判断是否 有信息没有能够从自变量中被提取出来。 不管是从拟合诊断面板中的残差图中,还是从自变量的残差图中, 我们都可以观察到,残差随机地分布在整个残差图上;从QQ图中,可 以判断残差基本服从正态分布,也就说,这组数据满足回归分析的假设 条件。除了在REG过程中通过QQ图和直方图直观检查残差的正态性以 外,也可以通过UNIVARIATE过程中的选项NORMAL对正态性进行严 格的检验(UNIVARIATE过程中已经介绍过),只是,这时需要先通过 OUTPUT语句将残差从REG过程中输出。 强影响点的判断可以借助于统计量RSTUDENT、Cook's D、 DFFITS和DFBETAS。在图15.16中,仔细观察右上两张RSTUDENT 图,超出上下两根横线(Cutoff)的点就是辨识出的强影响点,此外, 在Cook's D图中也找出了两个强影响点。在MODEL语句中使用选项 INFLUENCE,可以输出上述统计量,语法如下: MODEL因变量=自变量1 <自变量2 …>/ INFLUENCE; 接下来输出的是回归模型的拟合图,如图15.18所示。 拟合图提供了预测的精度,其中深颜色的部分是响应变量均值的 95%置信限,两条虚线表示响应变量的95%预测限。这里,Revenue的均 值的95%置信限,表示当自变量Square等于某一值时,Revenue的均值有 95%的可能性落在该置信限内;Revenue的95%预测限,表示当Square等 于某一值时,Revenue的取值有95%的可能性落在预测限内。在MODEL 语句中使用选项CLM和CLI可以输出置信限和预测限报表,语法如下: MODEL因变量=自变量1 <自变量2 …>/ CLMCLI; 图15.18 15.2.4 例15.3中回归模型的拟合图 模型选择 上面的例子只在模型中引入了一个自变量,当有多个自变量的时 候,如何进行自变量的筛选呢?是不是所有的自变量都应该被引入到回 归方程中呢?如果不是,该如何决定哪些自变量该保留下来,哪些自变 量该被剔除出模型呢?哪个模型又拟合得最好呢? 1.全部选择法 当用户在拟合回归方程时,若对自变量的选择没有任何先验经验, 可以在REG过程中的MODEL语句里使用选项SELECTION=,自动拟合 包含所有可能自变量组合的模型。例如,当有7个自变量时,每个自变 量都有进入模型或者不进入模型两种可能,那么总共就有27=128个可能 模型,在使用选项SELECTION=时,系统将自动拟合128个模型。使用 该选项的语法如下: MODEL因变量=自变量1 自变量2 … /SELECTION= RSQUAREADJRSQ CP BEST=n; 选项SELECTION=的取值可以为RSQUARE、ADJRSQ、CP中的任 意组合,例如,SELECTION=RSQUARE ADJRSQ CP或者 SELECTION=ADJRSQ RSQUARE CP。REG过程在输出模型时,将按照 排在第一位的统计量(RSQUARE、ADJRSQ、CP中的某一个)的取 值,对包含相同自变量个数的模型由好到坏进行排列,并同时输出其余 统计量。此外,MODEL语句中的选项BEST=n在选项SELECTION的不 同取值下有不同的作用效果。 ·当SELECTION=RSQAURE时,BEST=n使得系统将不同的模型按 所含自变量的个数分成不同的组,自变量个数相同的模型在同一组中, 在每一组中,输出前n个拟合得最好的模型。 ·当SELECTION=CP或者ADJRSQ时,选项BEST=n将使得系统输出 所有模型中拟合得最好的前n个模型。 例15.4:调用REG过程为响应变量Revenue拟合回归模型,考虑所 有可能的自变量。 示例代码如下: ods graphics / imagemap=on; title "Best Models Using All-Regression Option"; proc reg data=ex.retail plots(only)=(rsquare adjrsq cp); ALL_REG: model Revenue=Member Square Inventory Loyalty Population Tenure / selection=rsquare adjrsq cp; run; quit; 输出内容如图15.19所示。 图15.19 例15.4输出模型 图15.19是部分模型输出结果,因为总共有26=64个模型,可以通过 散点图来更直观地查看模型输出结果,如图15.20所示。R方图将所有模 型的R方值显示在了图中,随着模型中参数个数的增加,R方将增大, 按照R方的取值大小来看,显然全参数模型是最好的模型,因此,只能 通过R方比较具有相同参数数量的模型。调整后的R方图和先前的R方图 不一样,调整后的R方图中显示最好的模型是含2~3个参数(包含截距 项)的模型。 图15.20 例15.4输出的R方散点图和调整R方散点图 如图15.21所示是全部模型的Cp散点图。在观察Cp进行模型选择 时,有以下两个准则: ·一个是Mallows准则,满足该准则的模型的Cp必须小于等于p(p表 示模型包含的参数个数,截距项也看成一个参数,个数包含在p中), 该准则主要适用于预测。 ·另一个准则是Hocking准则,满足该准则的模型的Cp必须小于等于 2p-pfull+1,该准则主要适用于参数估计和对因变量的解释。 图15.21中直线Mallows代表p,当分析的目的是预测时,应重点考 察落在直线Mallows下面的模型;直线Hocking代表2p-pfull+1(pfull表示 所有参数的个数,截距项看成一个参数,个数包含在pfull中),当分析 的目的是进行参数估计和解释时,应重点考察落在直线Hocking下面的 模型。 图15.21 例15.4输出多个模型Cp的散点图 这里模型的Cp值比较密集,因此可以选择在图中只显示Cp比较小的 几个模型。代码如下: proc reg data=ex.retail plots(only)=(cp); ALL_REG: model Revenue=Member Square Inventory Loyalty Population Tenure / selection=cp rsquare adjrsq best=20; run; quit; 输出结果如图15.22和图15.23所示。 图15.22 例15.4中输出前20个模型 图15.23 例15.4输出前20个模型Cp的散点图 关于Mallows,建议选择满足条件Cp≤p条件且参数个数最少的模型 作为以预测为目的的回归模型,该模型中应包含变量Square;至于 Hocking,则建议选择满足条件Cp≤2p-pfull+1且参数最少的模型作为以解 释为目的的回归模型,该模型中应包含变量Square和Inventory。 在选择好变量之后,就可以和例15.3一样来拟合模型和进行参数估 计了。程序如下: title 'Check "Best" Two Candidate Models'; proc reg data=ex.retail ; Predict: model Revenue= Square ; Explain: model Revenue= Square Inventory; run; quit; 运行程序,结果如图15.24所示。 图15.24 例15.4输出REG 过程参数估计和假设检验报表 拟合诊断面板和残差图省略。 2.逐步选择法 在变量少、数据量小的情况下,全部选择方法是一个不错的选择, 可以在众多模型中挑选出最好的模型。但是在变量多、数据量大时,计 算量也会变得很大,当自变量个数达到20个时,可选模型就将达到100 万个,在这种情况下,全部选择方法肯定不是最好的方法。这里介绍另 一种选择法——逐步选择方法,其基本思想是,按照一定的规则,将自 变量逐个添加到或剔除出回归模型。 REG过程中的选项SELECTION=可以指定运用逐步选择法拟合模 型,SELECTION=有4个不同的取值,前3个取值分别代表3种不同的逐 步选择方法,最后一个取值表示使用所有自变量拟合模型。 ·FORWARD:称为向前选择法。第一步,建立包含一个自变量的 模型,这个自变量是所有自变量中最显著重要的一个;第二步,从其余 的自变量中选择一个自变量进入模型,使得进入的自变量是剩余自变量 中最显著重要的,重新拟合模型;重复第二步,直到剩余变量中没有变 量显著重要。选项SLENTRY=是FORWARD方法的引入变量的准则,默 认情况下,SLENTRY=0.5,当表示某个变量显著性程度的p值小于 SLENTRY时,则该变量进入模型。 ·BACKWARD:称为向后消除法。和FORWARD方法正好相反,初 始情况下,它会建立一个包含所有自变量的模型;第二步,保留模型中 的显著变量,剔除出最不显著的自变量,重新拟合模型;重复第二步, 直到模型中所有变量都是显著为止。选项SLSTAY=是BACKWARD方法 的不剔除变量的准则,默认情况下,SLSTAY=0.1,当表示某个变量显 著性程度的p值小于SLSTAY时,则该变量继续保留在模型中。 ·STEPWISE:称为逐步回归法,该方法综合了FORWARD方法和 BACKWARD方法。第一步和FORWARD方法一样,建立一个只包含一 个自变量的模型;第二步,在模型中引进新的自变量的同时,剔除现有 自变量中的不显著变量;重复第二步,直到所有自变量都被筛选完,并 且模型中的自变量都是显著的为止。默认情况下,选项 SLENTRY=0.15,SLSTAY=0.15,当某个变量已经进入模型时,若代表 某个变量显著性程度的p值小于SLSTAY,则该变量继续保留在模型 中;当某个变量还未进入模型时,如果将该变量纳入模型中,且代表其 显著性程度的p值小于SLENTRY,则将该变量选进模型中。 ·NONE:这是选项SELECTION的默认取值,表示用所有的自变量 拟合一个方程。 图15.25是FORWARD方法、BACKWARD方法和STEPWISE方法的 处理过程示意图。 需要注意的是,逐步选择法(FORWARD、BACKWARD和 STEPWISE方法)作为一种自动的模型选择方法也不是完美无缺的,研 究表明,运用逐步选择法进行模型选择的时候,自变量之间的共线性特 征会影响变量的引入或者剔除,极有可能导致丢失一些重要的变量,因 此,建议分别用逐步选择法中的3种方法拟合多个备选模型,同时更多 地了解自变量和因变量的关系,根据实际问题的意义来确定自变量的最 终选择和模型的拟合。 例15.5:分别使用FORWARD、BACKWARD和STEPWISE方法为 响应变量Revenue拟合回归模型。 示例代码如下: title "Best Models Using Stepwise Selection"; proc reg data=ex.retail plots(only)=adjrsq; forward: model Revenue=Member Square Inventory Loyalty Population Tenure/ selection=forward; backward: model Revenue=Member Square Inventory Loyalty Population Tenure/ selection=backward; stepwise: model Revenue=Member Square Inventory Loyalty Population Tenure/ selection=stepwise; run; quit; 输出内容如下。 第一步输出的是FORWARD方法(向前选择方法)的输出结果,如 图15.26所示。 图15.25 FORWARD、BACKWARD和STEPWISE方法示意图 图15.26 例15.5输出向前选择法第1步的内容 REG过程输出向前选择过程中每一步生成的模型的线性假设检验结 果和参数估计,并在最后输出一个汇总报表,如图15.27所示。 图15.27 例15.5输出向前选择法汇总报表 在汇总报表中,按顺序列出了每个自变量被引入模型的顺序。偏R 方中记录了每增加一个自变量后R方增加的量。在这个例子中,向前选 择方法选择的变量和全部选择法中按照Hocking准则选择的变量一样。 如图15.28所示是调整后的R方图,该图显示了每一步拟合的回归方 程的调整R方值。星号表示第2步拟合的模型是最好的模型。 图15.28 例15.5输出向前选择法调整R方图 接下来输出的是BACKWARD方法(向后消除法)的输出结果,如 图15.29所示。 此外,还输出了向后消除法的汇总报表,如图15.30所示。 在向后消除法中,变量Population、Tenure、Member、Loyalty和 Inventory依次被从回归方程中删除,最后回归方程中只含有一个变量 Square了。巧合的是,这和前面在全部选择方法中运用Mallows准则选 择出的变量一致。 调整后的R方图如图15.31所示,该图显示在第4步,也就是变量 Inventory没有被删除时,调整的R方值最大。 最后输出的是STEPWISE方法(逐步回归方法)的结果,图15.32显 示的是汇总结果。可以看到,逐步回归方法只进行了1步就停止了,也 就是说,在引入变量Square后,其余变量都不满足0.15的显著水平。 图15.29 图15.30 例15.5输出向后消除法第0步的内容 例15.5输出向后消除法汇总报表 图15.31 图15.32 15.2.5 例15.5输出向后消除法调整R方图 例15.5输出逐步回归法汇总报表 模型预测 根据上面模型选择方法,我们已经得到了回归方程 接下来,要运用回归方程对因变量进行预测。有以下3种方法可以实现 这一目的: ·手动编写回归方程,运用DATA步,计算出预测值。 ·将需要计算预测值的数据(因变量的值为缺失值)和建模的数据 纵向合并,调用REG过程建立模型,REG过程在拟合模型的时候,将会 忽略因变量为缺失值的观测。在MODEL语句中使用选项P,可使系统输 出所有观测的因变量实际值和所有观测(包含因变量为缺失值的观测) 的预测值。 ·运用SCORE过程,计算出预测值。 SCORE过程的基本语法如下: PROC SCORE DATA=数据集<SCORE=数据集><OUT=数据集><其他选项>; VAR 变量1 <变量2 … >; RUN; 其中: ·选项DATA=用来指定需要计算预测值的数据集;选项SCORE=用 来指定包含模型参数信息的数据集,SCORE过程会将该模型信息应用 到前一数据集中,进而计算出预测值,并通过选项OUT=输出包含预测 值的数据集。 ·VAR语句用来指定参与预测值计算的变量。其实SCORE过程是将 VAR语句中的变量值和模型参数数据集中对应的参数相乘,然后再将乘 项相加,从而得到预测值的。很多统计过程都可以输出包含模型参数信 息的数据集,并在SCORE过程中应用。 例15.6:计算当Square等于30、40、50、60、70时,Revenue的预测 值。 示例代码如下: data work.Need_Predictions; input Square @@; datalines; 30 40 50 60 70 ; run; proc reg data=ex.retail noprint outest=work.Betas; PreRev: model Revenue=Square; run; quit; title "OUTEST= Data Set from PROC REG"; proc print data=work.Betas; run; 在上述代码中,REG过程调用了选项OUTEST=输出模型参数。在 MODEL语句的前面使用了一个字符串PreRev加“:”,PreRev是该模型 的标签,也是预测值的变量名称,如图15.33所示。默认情况下,系统 将根据MODEL语句出现的顺序使用MODEL1、MODEL2等作为模型的 标签。 图15.33 例15.6中PRINT过程输出内容 下面的程序将调用SCORE过程为新的观测计算预测值。 proc score data=work.Need_Predictions score=work.Betas out=Scored type=parms; var Square; run; title "Score New Observations"; proc print data=work.Scored; run; 在SCORE过程中,选项TYPE的取值等于work.Betas中变量_TYPE_ 的取值,它代表选项SCORE指定的数据集的类型。输出内容如图15.34 所示。 图15.34 例15.6中生成的数据集work.score 预测值保存在变量PreRev中。 当模型中包含多个自变量时,同样可以运用SCORE过程进行预测 值的计算,对此,只需在SCORE过程中的VAR语句中对应指定多个变 量即可。 15.3 自变量间的共线性诊断 在进行多元线性回归的时候,自变量间的共线性问题容易导致模型 不稳定。但是,线性回归的假设中并没有要求自变量间不能存在共线 性,我们进行共线性的检验是出于模型稳定性的考虑。所谓“不稳定”指 的是当更换样本或者样本发生很小的变动时,模型就会发生很大的改 动,甚至模型根本就不再适用了。这一节中,将介绍如何判断自变量是 否存在共线性,哪些自变量存在共线性,以及在建模过程中如何使共线 性的影响达到最小。 在REG过程中提供了选项VIF、COLLIN和COLLINOINT让用户检 查自变量的共线性。本书中着重讲解选项VIF,有兴趣的读者可以参考 SAS帮助文档,查看选项COLLIN和COLLINOINT的用法及含义。 VIF是Variance Inflation Factor的缩写,VIF是由共线性造成的方差 增加的度量,中文也称为方差膨胀。在REG过程中,系统会对模型中的 每一个变量计算VIF, 这里的R2i是线性回归方程Xi=F(X1,X2, …,Xi-1,Xi+1,…,Xk)的R方。当VIFi>10时,表示Xi可能和某些变 量存在高度共线性,可能造成模型不稳定。 例15.7:调用REG过程,使用VIF选项判断哪些自变量存在高度共 线性。 示例代码如下: title "Collinearity - Full Model"; proc reg data=ex.retail; fullmodel: model Revenue=Member Square Inventory Loyalty Population Tenure/ vif; run; quit; 输出内容如图15.35所示。 图15.35 例15.7中输出内容 其中自变量Member、Loyalty和Tenure的VIF均大于10,这说明这些 变量间存在高度共线性。我们先尝试去除VIF值最大的自变量Member, 重新拟合模型,计算VIF。代码如下: proc reg data=ex.retail; fullmodel: model Revenue=/*Member*/ Square Inventory Loyalty Population Tenure/ vif; run; quit; 模型输出如图15.36所示。 在剔除掉变量Member之后,变量Loyalty和Tenure的VIF也降低,但 是仍然大于10,并且两个变量都不显著,这时可以继续尝试剔除变量 Tenure,因为Tenure的p值比Loyalty的p值大。重新拟合模型,计算 VIF。代码如下: proc reg data=ex.retail; fullmodel: model Revenue=/*Member*/ Square Inventory Loyalty Population /*Tenure*// vif; run; quit; 模型结果输出如图15.37所示。 图15.36 例15.7中重新拟合模型输出内容 图15.37 例15.7中第三次拟合模型输出内容 此时所有变量的VIF都小于10,说明模型中的自变量间不存在高度 共线性。 在逐步选择法中,由于存在共线性,可能导致有些显著的变量变得 不显著,重要的变量不能够被选进模型中。因此,建议在运用逐步选择 法建模前,先检查变量间的共线性问题,剔除一些导致高共线性的自变 量。具体方法可以参考第12章中的主成分分析法。 15.4 本章小结 本章介绍了统计中广泛应用的回归分析模型的基本原理、假设条 件、模型拟合和建模步骤,以及应用CORR过程和REG过程在SAS中的 实现。这里将对上面介绍的内容稍加总结。 回归分析基本上可以分为下面6个步骤进行: 1)探索分析,运用过程步(如,GPLOT过程、CORR过程)计算 变量的描述性统计量,作散点图,分析因变量和自变量之间的相关性。 2)调用回归分析的相关过程(如REG过程),在MODEL语句中使 用选项SELECTION进行变量和模型的选择,并进行模型和参数估计。 3)通过查看过程步的输出结果,进行回归分析假设条件的验证, 主要是查看残差图和检验残差的方差是否齐性。 4)进行变量间的共线性检查和样本中强影响点的检查。通过查看 REG过程中输出的各变量的VIF、COLLIN和COLLINOINT可进行共线 性检查;通过查看RSTUDENT残差、Cook's D统计量及DFFITS统计量 可进行强影响点的检查。 5)如果第3步和第4步的检验结果表明现有模型存在问题,则需要 重新进行模型选择和拟合,并返回进行第3步和第4步的检验。 6)模型验证。通常在建模前,如果样本数量允许,建议预留一部 分数据不参与建模,只用其中一部分数据进行建模。在拟合完模型后, 将模型应用到预留的数据中进行模型的验证。其中,用于建模的数据称 为训练数据(Training Data),用于验证的数据称为验证数据 (Validation Data)。 回归分析的流程如图15.38所示。 图15.38 回归分析流程 第16章 LOGISTIC回归分析 线性回归模型是一种流行的定量分析因变量与自变量之间相关关系 的统计分析方法。然而在许多情况下,线性回归都会受到限制。如,当 因变量是分类变量而不是连续变量时,线性回归就不适用。在许多社会 科学和商业分析中,需要研究的变量都是分类变量而不是连续变量。例 如,政治学中经常研究的是否选举某候选人;又如,商业分析中所涉及 的是否购买某种商品、是否回应一次促销活动,等等。这种选择度量通 常分为两类,即“是”与“否”。在调查研究中,态度与偏好等情感分析指 标也是按几个类型进行测量的,如“强烈反对”、“反对”、“中立”、“支 持”和“强烈支持”。甚至有些时候,人们更愿意将连续度量转换为分类 度量。例如,在分析学生升学考试成绩的影响因素时,虽然考试分数是 连续的,但往往只需要被划分为两类即可:录取线以上和录取线以下。 只要选定一个分界点,连续变量就可以被转换为二分变量。 在定量分析分类变量时,常用的一种统计方法是对数线性模型 (Log-linear model)。在本章中,将介绍对数线性模型的一种特殊形式 ——LOGISTIC回归模型。 16.1 基本原理 为了便于读者理解,将按与线性回归模型类比的方式来介绍 LOGISTIC回归模型。 16.1.1 线性概率模型 我们知道,线性回归模型没有对所使用的自变量的度量加以限制, 只是要求误差项独立服从于正态分布。其自变量可以是连续的,可以只 取正数和0值,也可以都是整数(如年龄),甚至可以是二分类型的 (如男性取1值,女性取0值)。然而,因变量却必须是连续的。要是在 线性回归模型中的因变量只取0和1两个值会怎么样? 如果用线性回归模型来解释某客户是否会拖欠银行贷款,所构想的 线性回归方程如下: yi=α+βxi+εi 其中,xi是第i个家庭的年收入;yi是一个二分变量,yi=1表示第i个 客户会拖欠银行贷款,否则,yi=0表示不会拖欠;残差项εi服从于均值 为0,方差为σ2的正态分布,并且εi与εj(i≠j)相互独立。 因为yi的取值只是0或者1,在给定xi的条件下,yi的期望值如下: E(yi|xi)=E(α+βxi+εi)=α +βxi=p(yi=1|xi)*1+p(yi=0|xi)*0=p(yi=1|xi) 其中,p(yi=1|xi)为事件发生的概率,对应的,事件不发生的概率 应该为p(yi=0|xi)=1-α-βxi。因此,因变量为二分变量的线性回归模型 也被称为线性概率模型。 线性回归模型的残差项为εi=yi-α-βxi。令f(εi)为残差项的密度函 数,定义当yi=0时,f(εi)=fi,假设残差的期望值为0,可得fi=1-αβxi,按照定义,εi的方差等于: Var(εi)=fi×(-α-βxi)2+(1-fi)×(1-α-βxi)2 代入可得: Var(εi)=p(yi=1|xi)×p(yi=0|xi) 由此可以看出,当因变量为二分变量时,运用线性回归模型分析因 变量时,由于残差的方差是依赖于因变量的值而变动的,对不同的观测 会有不同的方差,因此不再符合独立同分布的假设条件。这种情况在统 计中称为异方差性(heteroscedasticity)。 由于因变量的特殊性,用线性回归模型解释因变量存在以下问题: ·线性回归模型残差独立和残差方差齐性的假设条件不满足,使得 参数估计的估计方差是有偏的,因此任何假设检验都是无效的。 ·由于模型预测的是事件发生的概率,模型中的参数α和β是常数, 因此由模型估计的概率值有可能会超出[0,1]区间。 因此,当因变量为二分变量时,不再适合用线性回归模型来拟合因 变量与自变量之间的关系。那么,该用什么样的模型呢,这就是接下来 要讨论的内容。 16.1.2 LOGISTIC回归模型 根据前面的分析,建议对于二分因变量的分析采用非线性回归模 型。假设事件发生的条件概率p(yi=1|xi)与xi之间的非线性关系为单调 函数是合理的,即随着xi的增加(减少)p(yi=1|xi)也单调增加,考虑 到事件发生的条件概率的值域为(0,1),因此,这种曲线类似于一个 随机变量的累计分布曲线。最常用的分布函数是LOGISTIC分布。这里 先简要地描述一下把LOGISTIC函数用于二分因变量分析的理论依据。 假设有一个理论上存在的连续随机变量yi*代表事件发生的可能性, 其值域为-∞到∞。当该变量的值跨越一个临界点c时,便导致事件发生 了,即yi=1;否则yi=0,这里yi是实际观测到的因变量取值。假设随机 变量yi*和自变量xi之间存在线性关系,即, yi*=α+βxi+εi 于是,事件发生的概率为p(yi=1|xi)=p[(α+βxi+εi)>c]=p[εi>(-αβxi+c)],通常假设εi服从LOGISTIC分布,根据LOGISTIC分布的对称 性,则有: p[εi>(-α-βxi+c)]=p[εi≤(α+βxi+c)=F(α+βxi+c) 其中,F为εi的累积分布函数,这里就是LOGISTIC分布的累积分布 函数。为了方便表示,可以假设c=0,因此有: p(yi=1|xi)=F(α+βxi) 如果假设εi服从标准LOGISTIC分布,则累计分布函数可以有一个较为简 单的公式: 这个函数就是LOGISTIC函数,它具有S型分布,如图16.1所示。 图16.1 LOGISTIC函数曲线图 正如图16.1所示,无论xi取任何值,事件发生的条件概率 p(yi=1|xi)的取值范围均在0至1之间。 若将事件发生的条件概率p(yi=1|xi)记为pi,则pi为第i个观测发生 事件的条件概率,于是就得到以下LOGISTIC回归模型: 它是一个由自变量xi构成的非线性函数,但是这个非线性函数可以 转变成线性函数。 pi为第i个观测发生事件的概率,则事件不发生的条件概率为 那么,事件发生概率与不发生概率之比为: 这个概率之比称为事件的发生比,简称odds。可见odds的取值一定 是正值,并且无上界。将odds取自然对数,就可以得到: 这一转换称为logit形式,也称为y的logit,即logit(y)。logit(y) 对于其参数而言是线性的,并且依赖于自变量x的取值。模型中的参数α 和β可以按照一般回归系数那样进行解释。一个变量的作用如果是增加 对数发生比的话,也就是增加事件的发生比,反之亦然。 当有k个自变量时,LOGISTIC回归模型可以扩展如下: 相应的, 测。 其中pi=p(yi=1|x1i,x2i,…,xki)i表示第i个观 16.1.3 LOGISTIC回归模型的估计 在线性回归中,估计未知总体参数时主要采用最小二乘法,这一方 法的原理是根据线性回归模型选择的参数估计值,使得因变量的观测值 与预测值之间的残差平方和为最小。在线性回归分析中,极大似然估计 法可以得到与最小二乘法相同的结果。与最小二乘法相比,极大似然估 计法还可以用于非线性模型的参数估计。由于LOGISTIC回归是非线性 模型,因此,极大似然估计法是最常用的模型估计方法。 假设有n个观测作为样本,观测值为y1,y2,…,yn,对于1≤i≤n, yi的取值为0或者1,在给定xi的条件下,yi取值为1的概率记为pi;yi取值 为0的概率记为1-pi,于是,得到第i个观测值的概率为 由 于各观测是相互独立的,因此,可以得到它们的联合概率分布: 这也就是n个观测的似然函数。前面讨论过事件发生的条件概率为 代入似然函数中,极大似然估计的原理就是求使得似然函数取得 最大值的α和β的估计值。通常,在处理似然函数时,会两边取对数,则 得: 该式称为对数似然函数。 分别对参数α和β求偏导数,即可得似然方程。如果模型中有k个自 变量,那么就需要对βm求导,1≤m≤k,就可以得到k+1个方程,用来估 计α和β1、β2、…、βk的值。由LOGISTIC回归方程得到的似然方程是非 线性的,因此,这里极大似然估计都是通过迭代计算完成的。 在样本较大时,LOGISITC回归的极大似然估计具有相合性 (consistent)、渐近有效性(asymptotically efficient)和渐近正态性 (asymptotically normal)。相合性指当样本规模增大时,模型的参数估 计逐渐向真值收敛。渐近有效性是指当样本规模增大时,参数估计的标 准误相应缩小。渐近正态性是指随着样本规模的增大,极大似然估计参 数分布趋近于正态分布。因此,我们可以利用这一性质进行参数的显著 性检验,并且可计算参数的置信区间。 这些性质只有在样本较大时才能保持。那么,一个实际的问题是, 样本在多大时可以应用极大似然估计呢? 这个问题至今没有明确的答案。根据较普遍看法,中等规模的样本 (n=100)可以接受,在样本规模小于100时使用极大似然估计,对概率 估计值和置信区间的计算存在较大风险;若样本量大于500,使用极大 似然估计法就比较充分了。此外,样本规模的确定也依赖于模型和数据 的特点。如果模型中有很多参数需要估计,就需要较大的样本量。如果 数据条件不太好,比如自变量间具有高度共线性等问题存在,也需要较 大的样本量。 16.1.4 LOGISTIC回归模型的假设条件 要使得LOGISTIC回归模型的估计具有以上性质,除了要保证样本 的规模以外,还要必须满足一些假设条件。这些假设条件中有一些与线 性回归模型十分类似,包括: ·数据必须来自随机样本。 ·因变量被假设为k个自变量的函数。 ·LOGISTIC回归对共线性敏感,当自变量之间存在高度自相关时会 导致估计的标准误膨胀。 LOGISTIC回归模型也有一些不同于线性回归模型的假设,比如: ·LOGISTIC回归的因变量是一个二分变量,这个变量只能取值0或 者1,我们研究的是事件发生的条件概率。 ·LOGISTIC回归中因变量与各自变量之间的关系是非线性的。 ·线性回归模型中要求残差是独立同分布的,类似的假设在 LOGISTIC回归中不需要。 ·LOGISTIC回归中没有关于自变量分布的假设条件,自变量可以是 连续变量、分类变量等。 16.2 16.2.1 运用LOGISTIC过程拟合模型 基本语法 LOGISTIC过程可以用来拟合LOGISTIC模型,基本语法如下: PROC LOGISTIC DATA=输入数据集 选项; MODEL 因变量=<自变量1 自变量2 … ></选项>; SCORE <选项>; OUTPUT <OUT=输出数据集><关键字1=变量名1 关键字2=变量名2 … ></其他选项>; RUN; 其中: ·PROC LOGISTIC语句中指定输入数据集,常见的选项有 NOPRINT、PLOTS和NAMELEN=n。选项NOPRINT和PLOTS的用法与 其他过程中的类似。这里稍微介绍一下选项NAMELEN=,NAMELEN= 指定输入数据集和输出数据集中变量名的最大长度,n的取值在20和200 之间。在建模之前,有时需对变量进行各种各样的清理、转换和重新编 码,为了追踪这种处理,很多时候会在变量名上增加相应的描述,这样 就会导致变量名的长度增大。如果没有使用选项NAMELEN=,变量名 称可能会被截断。 ·MODEL语句用来指定因变量和自变量。因变量可以是二分变量, 也可以是定类变量(Nominal Variable)或者是定序变量(Ordinal Variable);自变量可以是连续变量或者分类变量。在LOGISTIC过程 中,必须有且仅有一个MODEL语句。 ·SCORE语句用来输出数据,输出数据中将包含输入数据集中的所 有数据,并且会输出预测事件发生的概率值。当然,也可以选择输出预 测的概率值的置信区间。可以使用多个SCORE语句。 ·OUTPUT语句中用选项OUT=指定输出数据集,输出数据集包含所 有变量和指定统计量。 建立统计模型通常所涉及的是个体数据,即每一条数据代表一个观 测,通过LOGISTIC过程的语法介绍可知,MODEL语句的语法也是针对 个体数据的情形。但是,LOGOSTIC过程使用不同的MODEL语句也可 以对分组数据建立LOGISTIC回归模型。分组数据指的是,每一条数据 代表多条观测,进行LOGISTIC回归分析的语法如下: PROC LOGISTIC DATA=输入数据集 选项; MODEL 因变量/ 试验次数= <自变量1 自变量2 … ></选项>; SCORE <选项>; OUTPUT <OUT=输出数据集><关键字1=变量名1 关键字2=变量名2 … ></其他选项>; RUN; 其中,在MODEL语句“/”后的试验次数指的是保存事件发生与未发 生次数的变量。需要注意的是,如果是对分组数据进行LOGISTIC回 归,LOGISTIC过程将规定因变量只能是二分型变量。 为了示范如何运用LOGISTIC过程拟合模型,下面采用一套模拟数 据来分析银行的个人消费贷款申请人拖欠贷款的可能性。 例16.1:数据集ex.loan是包含个体数据的数据集,包含了如下变 量。 ·ID:贷款编号,在数据集中是唯一的。 ·BAD:是否为不良贷款,如果是不良贷款,则BAD=1,如果不是 不良贷款,则BAD=0。 ·DELINQ:信用记录中拖欠交易次数,取值为0、1、2、3…。 ·EDUCATION:贷款申请人学历,取值为college、graduate、high school。 ·DEBTINC:贷款申请人资产负债率。 ·YROPEN:贷款申请人工作年限。 ·REASON:申请贷款用途,如果是用于商业投资,则REASON的 取值为business,如果是住房贷款,则REASON的取值为house,如果是 汽车贷款,则REASON的取值为car。 ·PLOAN:以前是否有过贷款,如果有过贷款记录,则PLOAN=1, 如果没有过贷款记录,则PLOAN=0。 现在要运用LOGISTIC过程来拟合申请人拖欠贷款的数据,模型的 因变量是BAD,要考虑自变量DELINQ和DEBTINC对因变量BAD的影 响,并对其估计过程和结果加以讨论。 示例代码如下: proc logistic data=ex.loan plots(only)=(effect (clband showobs)); model bad(event="1")= DELINQ DEBTINC; title 'Bad Loan Model'; run; 其中: ·选项PLOTS指定系统输出图形。 ·选项EFFECT指定输出关于自变量的预测概率效应图形,括号中的 子选项CLBAND和SHOWOBS指定在预测概率效应图形中显示预测概率 的置信域和观测。默认情况下,系统只输出关于一个自变量的预测概率 效应图形,当模型中有多个自变量时,系统将在使得其余连续自变量的 取值都为平均值、分类变量的取值为某一推荐值的情况下,输出第一个 连续自变量的预测概率效应图形。如果需要输出多副预测概率效应图 形,可以在选项EFFECT后面的括号内使用选项X=来指定需要作预测概 率效应图形的自变量,如有多个自变量,则需要用括号括起来,例如: Plots=(effect (x=(x1 x2 x3 ))); ·MODEL语句中,在因变量后面的小括号中使用选项EVENT=“1”, 表示BAD=1时事件发生,因此LOGISTIC回归模型计算的是BAD=1发生 的条件概率。选项EVENT的等号后面除了可以指定因变量的取值以 外,也可以使用关键字FIRST或者LAST。使用关键字FIRST或者LAST 时依赖于因变量取值的排列顺序,这个排列顺序可以使用选项ORDER= 进行指定。关键字FIRST表示对排在前面的因变量取值进行建模,默认 情况下,EVENT=FIRST;关键则LAST表示对排在后面的因变量取值进 行建模。注意,选项EVENT等号后面的值必须用括号括起来,当因变 量取值大于2个时,选项EFFECT失效。 输出结果的第一部分如图16.2所示(该结果为SAS 9.4英文环境下运 行结果)。 图16.2 例16.1中数据集简单描述 系统对建模用的数据集进行了简单的描述,包括数据集的名称、因 变量(也称响应变量)的名称及不同取值的个数、模型的种类和用来估 计模型参数的方法、读入及参与建模的观测的个数等。 响应概况报表中输出了因变量的取值和频数,默认情况下, LOGISTIC过程对因变量按升序排列,并对排在前面的因变量的取值进 行建模。如果没有使用选项EVENT,过程将对BAD=0进行建模。这里 因为使用了选项EVENT=“1”,因此过程输出了提示,表示LOGISTIC过 程将计算BAD=1的概率。 接下来,模型中输出了拟合优度信息,用来评价模型的拟合优度和 假设检验等信息。为了便于读者理解,下面在对例16.1的输出进行讲解 时将将穿插对基础概念的介绍。 16.2.2 假设检验 在线性回归中,我们假设模型具有线性形式,因此,在拟合线性模 型的过程中,需要对线性形式进行假设检验。同样的,在LOGISTIC回 归中,也需要对 的线性形式进行假设检验,原假设H0和备择假 设H1分别为: H0:β1=β2=…=βk=0 H1:β1,β2,…,βk不全为0 在LOGISTIC回归中,构造的统计量是χ2,其检验原理与线性回归 中类似。SAS的LOGISTIC过程使用3种方法进行检验,分别是似然比检 验(Likelihood Ratio)、评分检验(Score)和Wald检验。通过查看P 值,我们可以做出接受原假设或者拒绝原假设的判断:当P值小于给定 的显著性水平时,则可以拒绝原假设,认为 与x1,x2,…,xk具有 显著的线性关系;否则,接受原假设,认为所有βm=0,0<m≤k。在这3 种检验方法中,似然比检验方法相对于其他两种方法更加可靠,尤其是 在小样本的情况下。 在例16.1中,关于该假设检验部分的输出结果如图16.3所示。 图16.3 例16.1中LOGISTIC过程假设检验报表 这里P值<0.0001,所以应该拒绝原假设。 接下来LOGISTIC过程输出了参数估计。为了能够更好地解释模型 参数的意义,需要先介绍一下和模型参数相关的发生比和发生比率的概 念。 16.2.3 参数估计和解释 1.发生比(Odds) 在16.1节的基本原理部分,介绍了发生比是事件发生的概率与事件 不发生的概率的比值,即 既然odds是一个比值,因此其值域无上界,即可以在所有非负值域 取值。当比值大于1时,事件更可能发生。比如,一个事件发生的概率 为0.6,那么事件不发生的概率为0.4,于是发生比odds=0.6/0.4=1.5,这 意味着,事件发生的可能性是不发生的可能性的1.5倍。又比如, odds=0.25,则说明事件不发生的可能性是事件发生可能性的4倍。 2.发生比率(Odds Ratio) 发生比率是根据发生比计算出来的,用来比较两组数据中事件发生 比的指标。在LOGISTIC回归中,应用发生比率来解释自变量对事件发 生概率的作用。 接下来,用一个例子来说明发生比和发生比率的概念和关系。现有 180条观测,其中组A含有80条观测,组B含有100条观测,在组A中有60 条观测观察到事件发生,组B中有90条观测观察到事件发生,如图16.4 所示。 图16.4 计算发生比和发生比率的示例数据 那么,组A中事件发生的概率为60/80=0.75,组A中事件不发生的概 率为20/80=0.25,则组A中的发生比odds=0.75/0.25=3;组B中事件发生 的概率为90/100=0.9,组B中事件不发生的概率为10/100=0.1,则组B中 的发生比odds=0.9/0.1=9。因此,组B相对于组A的发生比率odds ratio= 组B中的发生比/组A中的发生比=9/3=3,它是组B与组A发生比之间的差 别测量。这一发生比率表示,组B中事件的发生比为组A中事件的发生 比的3倍。 这里,如果把组A和组B看成一个自变量的两种取值,那么发生比 率就可以理解为自变量对事件发生概率的作用。 ·大于1的发生比率表明事件发生的可能性会提高,或者说,自变量 对事件发生的概率有正的作用。 ·小于1的发生比率表示事件发生的可能性会降低,或者说,自变量 对事件发生的概率有负的作用。 ·发生比率为1表示自变量对事件发生概率无作用。 在了解了发生比和发生比率之后,再来看例16.1中的关于极大似然 参数估计部分的输出,如图16.5所示。 报表中给出了截距、DELINQ和DEBTINC的参数估计和假设检验结 果,这里是运用Wald卡方检验法对参数的显著性进行了检验,这里所有 参数的P值都小于0.0001,说明参数都是显著非零的。因此,可以将 LOGISTIC回归模型写成如下形式: ln(odds)=-4.4651+0.3713×DELINQ+0.0764×DEBTINC 可以看出,参数估计值实际上是相应自变量增加一个单位时 ln(odds)的改变量。例如,这里变量DELINQ改变1个单位时,即信用 记录拖欠次数增加1时,不良贷款发生比的对数取值将增加0.3713。 LOGISTIC回归模型也可以改写成: odds=exp(-4.4651+0.3713×DELINQ+0.0764×DEBTINC) 例如,自变量DELINQ增加一个单位时,odds将增大e0.3713=1.450 倍,e0.3713就是一个发生比的变化率,其实就是一个发生比率。在SAS 的LOGISTIC过程中,输出自变量增加一个单位时的发生比率和发生比 率的95%置信区间如图16.6所示(中文版的SAS将Odds Ratio Estimates翻 译成了优比估计,更准确的翻译应为发生比率估计)。 图16.5 例16.1中LOGISTIC过程极大似然参数估计分析报表 图16.6 例16.1中LOGISTIC过程发生比率估计 变量DELINQ的发生比率为1.450,表示当信用拖欠次数增加1个单 位时,不良贷款的发生比将提高1.450倍;变量DEBTINC的发生比率为 1.079,表示当资产负债率提高1个百分点时,不良贷款的发生比将提高 1.079倍。 概括起来讲,发生比、发生比率和模型参数估计之间有如下的关 系: ·当βk为正数时,eβk将大于1,说明自变量每增加1个单位值时,发 生比将会相应地增大eβk倍,增加1个单位前后的发生比率为eβk。 ·当βk为负数时,eβk将小于1,说明自变量每增加1个单位值时,发 生比将会相应地缩小eβk倍,增加1个单位前后的发生比率为eβk。 ·当βk=0时,eβk=1,说明无论自变量怎么变化,发生比都不会变 化。 将LOGISTIC回归模型表示成计算预测事件发生的概率的形式如 下: 通过该表达式就可以计算出每条观测中事件发生的预测概率了。 16.2.4 模型评价 在模型估计完成以后,需要来评价模型是否能够有效地描述样本数 据。这里可以从两个方面来看,一方面是查看模型的拟合优度,一方面 是检查模型的预测准确性。 1.拟合优度评价 在统计中,有多种方法可以对LOGICTIC回归模型的拟合优度进行 评价。常用的评级指标是信息测量类的指标,即AIC准则(Akaike's A Information Criterion)和SBC准则(也称为SC准则:即Schwarz's Bayesian Information Criterion)。在SAS的LOGISTIC过程中,AIC和SC 的计算公式如下。 AIC准则的公式为: AIC=-2log(L)+2k 其中,L为似然函数的取值,k是参数的个数。 SBC准则的公式为: AIC=-2log(L)+klog(n) 其中,n为样本容量。 在这里,AIC准则和SC准则只适用于同一数据不同模型之间的比 较,也就是说,其取值不适合用于不同数据的模型之间进行比较。在其 他条件相同时,模型的AIC或者SC取值越小说明模型拟合得越好。 在例16.1中,模型的拟合优度信息如图16.7所示。 2.预测准确性 在这里要介绍的一类评价LOGISTIC回归模型预测准确性的方法, 是建立在因变量取值与模型预测概率之间的关联性基础之上的。有若干 种这样的指标可用来评价这种关联性,下面将介绍SAS的LOGISTIC过 程默认输出的相关指标,包括指标Gamma、Somers'D、Tau-a和c。 LOGISTIC回归模型的因变量只有两种可能值(0或者1,发生或者 不发生),我们可以按事件是否发生将观测分成两组,每组中各取一条 观测,形成一个观测数据对。如果观测到事件发生组的观测条数为 100,观测到事件未发生组的观测条数为200,则总共有100×200=20000 个观测对。在一个观测数据对中,如果事件发生的观测的预测概率值大 于事件未发生的观测的预测概率值,就定义该观测数据对为和谐对 (concordant);如果事件发生的观测的预测概率值小于事件未发生的 观测的预测概率值,就定义该观测数据对为不和谐对(discordant)。如 果一个观测数据对既不是和谐对,又不是不和谐对,也就说,事件发生 的观测的预测概率值等于事件未发生的观测的预测概率值,那就定义该 观测数据对为结(Tie)。相关指标的计算公式如下: 其中,n为样本容量,t为总的观测数据对数,nc是和谐对的数量, nd是不和谐数据对的数量。这些指标用于同一组数据的不同模型之间的 比较,指标的取值越大,说明模型的预测准确度越高。指标c和 Somers'D在应用于比较LOGISTIC模型时通常较好。一个模型的c=0.613 代表了使用该模型时,观察到事件发生的观测的预测概率值比观察到事 件未发生的观测的预测概率值更大的可能性为0.613,它是表示模型区 分度的指标。 下面来看例16.1关于预测准确度的输出结果,如图16.8所示。 图16.7 图16.8 例16.1中模型拟合优度统计量报表 例16.1中模型的预测准确度信息 在图16.8中,输出报表的第2列输出了和谐对、不和谐对、结的百 分比,也输出了总的观测数据对。这里,我们知道观察到事件发生的观 测有357条,观察到事件未发生的观测有1458条,因此观测数据对为 357×1425=508725,其中和谐对占了68.1%,不和谐对占了31.4%,结占 了0.5%。根据上面介绍的公式,就可以分别计算出4个指标的取值,其 中c=0.684。 例16.1最后还输出了关于连续自变量DELINQ的预测概率效应图, 如图16.9所示。其中,深色区域是预测概率的95%置信区域。该例中 LOGISTIC模型含有两个自变量,因此在作关于DELINQ的预测概率效 应图时,另一个自变量DEBTINC的取值为自身的均值。图形表示自变 量DELINQ和BAD的关系是正向的,当信用记录拖欠交易次数增加时, 发生贷款拖欠的可能性增大。 图16.9 例16.1中DELINQ的预测概率效应图 16.3 16.3.1 LOGISTIC过程的其他语句 CLASS语句 我们在建立LOGISTIC回归模型时难免会遇到分类变量,例如表示 性别(男,女)的变量,表示地域、省份的变量,表示商品种类的变 量,以及一些次序变量。遇到这种类型的变量时,需要在LOGISTIC过 程中使用CLASS语句。CLASS语句将在拟合LOGISTIC回归模型之前, 对这些分类变量进行预处理,生成一些新的变量,然后将这些新生成的 变量用于LOGISTIC回归。 使用CLASS语句的基本语法为: CLASS分类变量1<(选项)>分类变量2<(选项)><分类变量3> … </全局选项>; CLASS语句必须用在MODEL语句之前,CLASS语句中的大部分选 项既可以作用在单个分类变量上,也可以作用在CLASS语句指定的所有 分类变量上。当选项只作用于单个分类变量时,选项必须写在分类变量 后面的小括号中。作用于所有分类变量的选项也称为全局选项,必须写 在所有分类变量之后的“/”后面。当同时使用全局选项和作用于单个分类 变量的选项时,作用于单个分类变量的选项的优先级高。 CLASS语句中常用的选项如下: ·选项PARAM=关键字,用来指定对分类变量进行参数化的方法, 常用的关键字有EFFECT、REFERENCE、ORDINAL等。 PARAM=EFFECT是默认选项。 ·当PARAM=EFFECT时,如果分类变量有k个不同的取值,也称k 个水平,则系统将自动创建k-1个新的变量。以分类变量VAR1为例, VAR1有4个不同的取值1、2、3、4,则系统将创建3个新的变量D1、D2 和D3,这3个新变量的赋值规则如图16.10所示。 此时,新变量的参数估计值表示,和平均水平相比,当变量处于某 一固定水平(非参考水平)时,发生比的对数的改变量。 这里面还需要提到的是,当VAR1的取值为某一特定水平时,所有 新变量的取值都为-1。这个特定水平称为分类变量的参考水平 (Reference Level),可以使用选项REF='level'|关键字进行指定(下面 介绍)。 当VAR1的取值为非参考水平时,每个新变量的取值只在VAR1为 某一固定水平时为1,其余情况下都为0。 ·当PARAM=REFERENCE时,如果分类变量有k个水平,则系统 将自动创建k-1个新的变量,赋值规则和PARAM=EFFECT时类似,但 是,当分类变量的取值为参考水平时,所有新变量的取值为0。以上面 的VAR1为例,新变量的赋值图16.11所示。 图16.10 图16.11 3个新变量的赋值规则(1) 新变量的赋值规则(2) 此时,新变量的参数估计值表示,和参考水平相比,当变量处于某 一固定水平(非参考水平)时,发生比的对数的改变量。 ·PARAM=ORDINAL用于定序变量,系统将根据定序变量的不同 水平(默认情况下,按水平的升序)创建出新的变量。此时,新变量的 参数估计值表示定序变量两个连续水平下发生比的对数的改变量。 ·选项REF='level'|关键字,用来指定参考水平,PARAM=EFFECT和 PARAM=REFERENCE时都需要指定参考水平。 16.3.2 ODDSRATIO语句 上面介绍了不同的参数化方法下参数估计值的意义,基于此,可以 比较某一水平相对于平均水平或者某一水平相对于参考水平下的发生比 率。如果希望比较分类变量任何水平之间的发生比率,则需要使用 ODDSRATIO语句,使用语法如下: ODDSRATIO<'LABEL'>自变量</选项>; ODDSRATIO语句中的常用选项如下: ·选项CL=WALD|PL|BOTH,用来指定计算置信区域的方法。当 CL=PL时,系统将根据剖面函数计算置信区间,样本容量较小时,建议 指定CL=PL;当CL=WALD时,系统将根据WALD检验计算置信区间; 当CL=BOTH时,系统将分别用两种方法来计算置信区间。 ·如果自变量是分类变量时,选项DIFF=REF|ALL用来指定是否相对 于参考水平计算发生比率,默认情况下,DIFF=ALL表示比较所有水平 间的发生比率;如果自变量是连续变量,选项DIFF将被忽略。 如果自变量是连续变量时,ODDSRATIO语句将输出自变量的发生 比率。一个LOGISTIC过程中可以使用多个ODDSRATIO语句。 16.3.3 UNITS语句 在实际操作中,我们常常对一些连续变量中一个单位值的变化不感 兴趣,比如,年龄增加1岁或收入增加1元的作用非常微小,而相对离散 的变化,如年龄增加5岁或收入增加100元的变化也许更有意义。在 LOGISTIC过程中,为了计算特定单位变化的发生比率,需要使用 UNITS语句。UNITS语句的基本语法如下: UNITS <自变量1=特定单位列表<自变量2=特定单位列表…>></选项>; 其中: ·UNITS语句中指定的自变量必须是连续型自变量。 ·特定单位列表中可以包含非零数字、SD或者-SD、非零数字*SD, 它们之间用空格隔开。SD表示自变量的样本标准差。例如,VAR1=-2 指定系统计算当自变量VAR1减少两个单位时的发生比率;VAR1=2*SD 指定系统计算当自变量VAR1增加两个样本标准差时的发生比率。 ·“/”后的选项有DEFAULT=特定单位列表,用来为未出现在UNITS 语句中的自变量指定特定单位列表。如果没有使用选项DEFAULT=,过 程将不会为未出现在UNITS语句中的自变量计算特定单位变化下的发生 比率。 例16.2:运用LOGISTIC过程来拟合银行不良贷款数据,数据保存 在数据集ex.loan中。模型的因变量是BAD,考虑的自变量包括连续性变 量DELINQ、DEBTINC和YROPEN,分类变量为EDUCATION、 REASON、PLOAN。要求如下: ·使用UNITS语句计算当贷款申请人资产负债率(DEBTINC)变化5 个单位时发生比的变化率。 ·所有的分类变量都必须在CLASS语句中指定。 ·EDUCATION的参考水平为college,REASON的参考水平为car。 ·在参数估计的时候输出标准参数估计。 ·使用ODDSRATIO语句计算REASON所有水平之间的发生比率。 ·输出自变量DELINQ、DEBTINC和EDUCATION的预测概率效应 图。 ·输出自变量REASON和EDUCATION的发生比率图。 示例代码如下: proc logistic data=ex.loan plots(only)=(effect (clbandx=(DELINQ DEBTINC REASON)) oddsratio (type=horizontalstat range=clip)); class EDUCATION(ref="college") REASON(ref="car")/param=reference; model BAD(event="1") = DELINQ DEBTINC YROPEN EDUCATION REASON/clodds=pl stb parmlabel; units DEBTINC=5 -5; oddsratio EDUCATION/diff=all cl=pl; oddsratio REASON/diff=all cl=pl; title "Bad Loan Model"; run; 上述程序完成了以上7项要求,这里对程序中所用的选项稍加说 明。 ·PROC LOGISTIC语句中的选项ODDSRATIO指定系统将默认输出 的发生比率和置信区域,以及将ODDSRATIO语句、选项CLODDS中输 出的发生比率和置信区域用图形展现出来。选项ODDSRATIO的常用子 选项为TYPE和RANGE,它们必须置于ODDSRATIO后面的小括号中。 TYPE=HORIZONTALSTAT将在图形的右端显示发生比率的值和置信区 间,RANGE=CLIP指定图形的横坐标的取值范围为从计算得到的最小的 发生比率到最大的发生比率。 ·PROC LOGISTIC语句中选项EFFECT的子选项X指定需要做预测概 率效应图形的自变量,如果有多个自变量则需要用括号括起来。 ·CLASS语句中的选项PARAM指定分类变量参数化的方法。 ·CLASS语句中的选项REF指定参考水平。 ·MODEL语句中的选项CLODDS指定输出发生比率的置信区间。当 CLODDS=PL时,系统将根据剖面函数计算置信区间,样本容量较小 时,建议指定CLODDS=PL;当CLODDS=WALD时,系统将根据 WALD检验计算置信区间;当CLODDS=BOTH时,系统将分别用两种 方法来计算置信区间。 ·MODEL语句中选项STB表示系统将输出标准化系数,根据标准化 的LOGISTIC回归系数可以比较有着不同度量的自变量对事件发生比及 概率的作用。应当注意的是,通常标准化系数是用来比较不同度量的连 续自变量的作用大小的,因此,对于分类变量如性别、教育程度等,标 准化系数也就没有意义了。 ·MODEL语句中的PARAMLABEL为在极大似然估计报表中输出变 量的标签。 运行上述代码后的输出结果如下(该结果为SAS 9.4英文环境下运 行的结果)。 如图16.12所示,分类水平信息报表中显示分类变量EDUCATION被 参数化为两个新的变量了,因为PARAM=REF和REF=college,因此 EDUCATION=college是参考水平;同样,REASON也被参数化为两个 新的变量了,REASON=car是参考水平。变量PLOAN只有两个取值,因 此只需要创建一个新的变量即可,PLOAN=0是参考水平。 在图16.13中,模型拟合统计量报表输出了模型的AIC和SC值,便于 多个模型之间的比较。 图16.12 例16.2中数据集简单描述和分类水平信息报表 图16.13 例16.2中模型拟合优度统计量报表和假设检验报表 似然比检验法、评分检验法和WALD检验法的p值都小于0.0001, 说明Logit与自变量之间的线性关系是显著的。 3型效应分析报表中的输出显示了模型中的哪些变量是显著的,哪 些变量是不显著的。在0.05的置信水平上,变量DELINQ、DEBTINC、 YROPEN、REASON和PLOAN都是显著的。 极大似然估计分析报表中给出了截距和自变量的参数估计值(也就 是回归系数)、参数估计的标准误差,以及运用Wald卡方检验法对参数 的显著性进行检验的结果,此外由于在MODEL语句中使用了选项 STB,因此同时也输出了标准化估计值,如图16.14所示。对于标准化估 计值,只需要关注模型中连续自变量的标准化估计即可,分类变量的标 准化估计值意义不大,可以不做参考。 图16.14 例16.2中极大似然估计分析报表 根据LOGISTIC回归模型的公式可知,自变量的回归系数代表了当 自变量增加1个单位,其余自变量都保持不变时,logit的改变量。例 如,前面的示例中,当DEBTINC增加1个单位,其余自变量都保持不变 时,拖欠贷款的发生比的对数将增加0.0709。 WALD卡方检验用于检查参数是否显著非零。例如,这里的变量 EDUCATION,在0.05的显著性水平上,标签为EDUCATIONgraduate一 行的参数估计,表示当EDUCATION取值为graduate时,与参考水平 college相比,发生比的对数的改变量,同时WALD卡方检验的p值表示 该参数估计不是显著非零的。对于变量REASON,在0.05的显著性水平 上,标签为REASON business一行的参数估计表示,当REASON的取值 为business时,与参考水平car相比,发生比的对数的改变量为1.5111, 并且是显著非零的。 由于各自变量的度量是不一样的,因此在进行自变量对模型作用的 比较时,直接拿自变量的回归系数进行比较是不合适的,这时需要运用 标准化估计值来进行比较。其实,这点和线性回归分析中是一样的,但 是在LOGISTIC回归和线性回归中,计算标准化估计值的方法是有区别 的,有兴趣的读者可以查阅SAS帮助文档。标准化估计值的绝对值可以 用来比较自变量对模型的作用,需要指出的是,根据分类变量创建的新 的变量只有两种取值,标准化后没有实际意义,基于此计算的标准化参 数可以不做参考。 在这个例子中,虽然变量DELINQ的参数估计值(0.4030)比变量 DEBTINC的参数估计值(0.0709)大,但是变量DELINQ的标准化估计 值(0.2808)比变量DEBTINC的标准化估计值(0.3387)小,说明相比 较而言,变量DEBTINC的预测能力较大。 在图16.15中,C值为0.853,表示观察到事件发生的观测比观察到事 件未发生的观测的预测概率更大的可能性为0.853。 图16.15 例16.2中模型的预测准确性信息 因为在程序中使用了ODDSRATIO语句指定系统对分类变量 EDUCATION和REASON各个水平的发生比率进行比较,因此输出了发 生比率估计(Odds Ratio Estimates)和剖面似然置信区间(ProfileLikelihood Confidence Intervals)报表,如图16.16所示。其中,Estimate 一列输出了发生比率。这里稍微解释一下标签列的意义,举例来说, Reason business vs car表示Reason的取值为business与Reason的取值为car 进行对比,发生比率的估计=Reason为Business时的发生比除以Reason为 car时的发生比。在95%Confidence Limits包含的两列中,只有Reason business vs car和Reason business vs house的95%置信限中不包含1(发生 比率等于1时,表示不同水平对于发生比是无差别的),因此,Reason business vs car的发生比率和Reason business vs house的发生比率是显著 的。 图16.16 例16.2中发生比率估计和剖面似然置信区间报表 当发生比率大于1时,例如,Reason business vs house的发生比率为 3.197,表示Reason为business时拖欠贷款的发生比比Reason为house时拖 欠贷款的发生比提高了3.197倍。 PROC LOGISTIC语句中选项PLOTS将发生比率估计和剖面似然置 信区间用图形展示出来,如图16.17所示。可以看到,只有Reason business vs car和Reason business vs house的95%置信区间和发生比率为1 的参考线不相交,说明Reason business vs car的发生比率和Reason business vs house的发生比率是显著的。 图16.17 例16.2中发生比率估计和剖面似然置信区间图 图16.18展示的这张报表也是一张发生比率估计和剖面似然置信区 间报表,因为在MODEL语句中使用了选项CLODDS=PL,故输出了该 报表。UNITS语句的使用,使得报表中输出了当自变量DEBTINC增加 或减少5个单位时的发生比率及置信区间估计。观察报表,由于我们在 UNITS语句中只指定了连续自变量DEBTINC及其特定单位列表,因此 这里只输出了连续自变量DEBTINC和所有分类变量的发生比率。如果 需要输出所有连续自变量的发生比率,需要在UNITS语句中使用选项 DEFAULT。该报表的分析方法和图16.17中的报表一样。 图16.18 例16.2中对应UNITS 语句发生比率估计和剖面似然置信区间报 表 现在考虑一下发生比率小于1时如何解读变量,例如,当变量 DEBTINC减少5个单位时,发生比变为原来的0.701倍。也可以通过百分 比的方式来表达,(0.701-1)*100%=-29.9%,即当资产负债率每减少5 个单位时,拖欠贷款的发生比降低了29.9%。 同样,PROC LOGISTIC过程中的选项PLOTS将上面的发生比率估 计和剖面似然置信区间报表也用图形展示出来,如图16.19所示。 图16.19 例16.2中对应UNITS语句时发生比率估计和剖面似然置信区间 图 接下来输出的是预测概率效应图。由于子选项X=(DELINQ DEBTINC REASON),因此分别输出了3个关于自变量的预测概率图。 当模型中含有多个变量时,在做某一个自变量的预测概率图时,其余连 续自变量的取值都为均值,分类自变量的取值为参考水平。 DELINQ和DEBTINC都是连续变量,两个变量和预测概率之间的关 系都是正向的,如图16.20和图16.21所示。当DELINQ(信用拖欠交易 次数)的取值越大时,预测拖欠贷款发生的概率越大;当 DEBTINC(资产负债率)的取值越大时,预测拖欠贷款发生的概率越 大,这也是符合规律的。 图16.20 例16.2中DELINQ的预测概率效应图 图16.21 例16.2中DEBTINC的预测概率效应图 分类变量REASON的预测概率效应图如图16.22所示,该图和连续 变量的预测概率效应图有一些差别。分类变量的预测概率效应图的横坐 标是分类变量的不同水平,长方形的上下两条边表示预测概率的95%置 信上限和置信下限,长方形中间的圆圈表示预测概率。图形表示,当 REASON的水平为business时,预测概率较大。 图16.22 例16.2中REASON的预测概率效应图 16.4 建立模型 前面章节已经介绍了LOGISTIC函数、LOGISTIC回归模型的假设检 验、参数估计、模型评价,以及运用SAS LOGISTIC过程的拟合模型, 本节将讨论建立模型的有关问题。 16.4.1 自变量与Logit值的关系 在建模初始阶段,普遍的做法是假设LOGISTIC回归是关于自变量 的线性函数。不管一个自变量与因变量之间的关系是线性的还是非线性 的,只要这个变量真的重要,拟合线性模型总是可以得到显著的回归系 数。但是一旦确定这个变量很重要,就要从建立模型的角度去考虑如何 取得正确的参数估计。一个以自变量线性组合的LOGISTIC回归模型也 许在函数形式上就是不正确的,也就是说,很有可能Logit(pi)并不是 自变量的线性组合函数,而是自变量的非线性组合的函数,例如,包含 自变量的二次项和自变量的交叉相乘项等。 如何探索Logit(pi)和自变量之间是否为线性或者非线性关系呢? 常见的一种方法是,将相应的连续自变量或定序变量的取值分成若干 组,在每组中找出因变量的平均值。这种方法也适用于LOGISTIC回 归,对每个组找出组中的logit值和自变量的均值,并由此作出一条曲 线,那么这条曲线称为Logit图。通常,建议每组中的观测条数一样,并 且要包含不少于15条观测。 如果logit(pi)与自变量之间存在线性关系,那么所画出的点便会 落在一条直线上,如果画出的点不在一条直线上,便说明存在 logit(pi)与自变量之间存在着非线性关系。公式如下: 当某一组中出现事件发生的概率为1或者为0时,logit的取值将趋向 于∞或者-∞。为了避免这种情况的发生,要在 的分子和分母中都加上 一个很小的常数项: 图16.23展示了两组关于自变量的Logit图,左图中自变量和 Logit(pi)呈线性关系,满足LOGISTIC回归模型中关于Logit(pi)的 线性假设;右图中自变量和Logit(pi)呈二次函数关系,可以考虑在 Logit(pi)的线性模型中添加自变量X的二次项,或者根据自变量X的 取值创建两个虚拟变量X1和X2,再将交叉项X·X1和X·X2引入 Logit(pi)的线性模型中,从而提高模型的拟合效果。 图16.23 Logit图示例 例16.3:根据数据集ex.loan中的数据制作关于自变量DEBTINC与因 变量BAD的Logit图。 示例代码如下: proc sort data=ex.loan out=work.loan; by DEBTINC; run; proc rank data=work.loan out=work.ranks groups=50; var DEBTINC; ranks rk; run; proc means data=work.ranks noprint nway; class rk; var DEBTINC BAD; output out=work.bins sum(BAD)=BAD mean(DEBTINC)=DEBTINC n(BAD)=counts; run; data work.bins; set work.bins; logit=log((BAD+0.5)/(counts-BAD+0.5)); run; proc sgplot data=work.bins; reg x=DEBTINC y=logit; scatter x=DEBTINC y=logit; yaxis label="Estimated Logit"; title "Estimated Logit Plot of DEBTINC"; run; 在上述代码中,由于DEBTINC是连续变量,因此先调用RANK过程 将根据DEBTINC取值的观测等分成50个组(建议组数=总观测数/每组观 测数,每组观测数可以在20到30之间),再调用MEANS程序计算出每 组的频数、每组中事件发生的频数和每组中自变量DEBTINC的均值 (参考第9章中MEANS过程),然后通过DATA步计算出每组中的Logit 值,最后调用SGPLOT过程作出Logit的散点图和回归直线。 Logit图如图16.24所示。 图16.24 变量DEBTINC的Logit图 在上述图形中,除了第一组的Logit值外,没有明显的证据表明 DEBTINC和Logit之间存在非线性的关系。 16.4.2 自变量的互动作用 当因变量受某个自变量影响时,该自变量对因变量影响的大小同时 还依赖于一个或者多个其他自变量的取值,那么称为该自变量与其他自 变量间存在互动作用。例如,受教育程度和贷款原因的互动意味着受教 育程度对是否拖欠贷款的作用还依赖于贷款原因。换句话说,对于不同 的贷款原因,受教育程度的影响是不一样的。 互动项是通过一个高次项加入模型的,让我们来看一个包含互动项 的LOGISTIC回归模型的例子。 其中,x1·x2是一个互动项。如果系数β3显著区别于0,就表明x1和 x2存在互动作用。 在LOGISTIC过程中引入互动项的语法如下: PROC LOGISTIC DATA=输入数据集选项; MODEL 因变量=自变量1 自变量2 自变量1*自变量2 ... </选项>; RUN; 模型中引入了自变量1和自变量2的互动项。 然而,当模型中有很多自变量时,人们通常只检验那些特别可能的 互动项,或者检验所有可能的互动项。例如,对只有3个自变量(x1, x2,x3)的模型,可能的互动项如下:x1·x2、x1·x3、x2·x3、x1·x2·x3, 前3个称为二次互动项,第4个互动项称为三次互动项。在一个相对简单 的模型中,检验所有可能的互动项还是有一定的可能的,在LOGISTIC 过程中,也提供了比较简单的语法以便在模型中引入多个互动项,语法 如下: PROC LOGISTIC DATA=输入数据集选项; MODEL 因变量=自变量1 自变量2 自变量3 ...自变量k 自变量1|自变量2| 自变量3|...|自变量k @2 </选项>; RUN; 其中,竖线和@2表示在LOGISTIC回归模型中引入自变量1、自变 量2、…、自变量k的所有可能的二次互动项。如果@后面的数字是3, 则表示在模型中引入自变量1、自变量2、…、自变量k的所有可能的二 次互动项和三次互动项。 随着模型中的自变量数量的增加,也就给检验自变量的互动作用带 来了巨大的计算量,通常在实际操作中,我们的做法是只检验那些在实 际中可能性较大并具有一定商业意义的互动项。 16.4.3 模型选择 为了建立模型,可以任意在模型中加入自变量,然后进行模型比较 检验,以选择拟合数据较好的模型。在介绍REG过程的时候,已经介绍 了模型选择的3种逐步选择法,同样的,在LOGISTIC过程中,也有相应 的逐步选择法。使用模型选择方法的语法如下: MODEL 因变量=自变量1 自变量2 自变量3 ...自变量k / SELECTION=FORWARD|BACKWARD|STEPWISE|SCORE ; 选项SELECTION=有以下4种取值: ·FORWARD称为向前选择法。第一步,建立包含一个自变量的模 型,这个自变量是所有自变量中最显著的一个;第二步,从其余的自变 量中选择一个进入模型,使得进入的自变量是剩余自变量中最显著重要 的,重新拟合模型;重复第二步,直到剩余变量中没有变量显著重要。 选项SLENTRY=是FORWARD方法引入变量的准则,默认情况下, SLENTRY=0.05。 ·BACKWARD称为向后消除法,和FORWARD方法正好相反。第一 步,初始情况下,建立一个包含所有自变量的模型;第二步,保留模型 中的显著变量,剔除最不显著的自变量,重新拟合模型;重复第二步, 直到模型中所有变量都是显著的为止。选项SLSTAY=是BACKWARD方 法不剔除变量的准则,默认情况下,SLSTAY=0.05。 ·STEPWISE称为逐步回归法,综合了FORWARD方法和 BACKWARD方法。第一步和FORWARD方法一样,建立一个只包含一 个自变量的模型;第二步,在模型中引进新的自变量,同时,剔除现有 自变量中的不显著变量;重复第二步,直到所有的自变量都被筛选完, 并且模型中的自变量都是显著的为止。默认情况下,选项 SLENTRY=0.05,SLSTAY=0.05。 ·SCORE和REG过程中的全部选择法类似,过程将自动拟合2k-1个 模型,其中k为回归项的个数。通常SELECTION=SCORE会与选项 BEST=m同时使用,作用是:将不同的模型按所含自变量的个数分成不 同的组,自变量个数相同的模型在同一组,每一组中,根据卡方统计 量,选择前m个模型。需要注意的是,当SELECTION=SCORE时, LOGISTIC过程中不能使用CLASS语句,因此需要在调用LOGISTIC过 程拟合模型之前对分类变量进行参数化。 若模型中引入了自变量的互动项,LOGISTIC过程将通过MODEL语 句中的选项HIERARCHY=来控制自变量和自变量的互动项是进入还是 剔除出模型,使用语法如下: MODEL 因变量=自变量1 自变量2 自变量3 …自变量k 自变量1*自变量2 自变量1*自变量3 … / HIERARCHY=SINGLE|MULTIPLE|NONE ; 选项HIERARCHY=有以下3种取值,在 SELECTION=FORWARD|BACKWARD|STEPWISE时起作用。 ·SINGLE:这是选项HIERARCHY=的默认取值,当进行逐步模型 选择时,每次只能有一个自变量或自变量的互动项被选进或剔除出模 型,并且,当自变量的互动项在模型中时,构成互动项的自变量必须在 模型中。 ·MULTIPLE:当使用逐步选择法时,保持基本原则,当某自变量 的互动项在模型中时,构成该互动项的自变量必须在模型中。但是,进 行逐步模型选择时,如果某个自变量由于显著性被选入模型,那么它的 互动项也可能同时被选入模型中(要求构成互动项的其他自变量也在模 型中);如果某个自变量的交互项由于不显著而被剔除出模型,那么构 成该互动项的自变量也有可能同时被剔除出模型。与 HIERARCHY=SINGLE时不一样的是,不再要求每一步中只能有一项 (自变量或者自变量的互动项)进入或者剔除出模型。 ·NONE:此时进行逐步模型选择时,不再要求当自变量的互动项在 模型中时,构成互动项的自变量必须在模型中。 如果模型中包含自变量互动项,并且也包含构成互动项的所有自变 量,那么则称模型保持了层级结构。如果模型保持了层级结构,那么即 使分类自变量指定了不同的参数化方法(第16.3.1中介绍过),模型的 检验结果仍是保持不变的。如果模型没有保持层级结构,那么对不同的 参数化方法,检验结果可能有差异。 LOGISTIC过程的自动逐步选择法使得我们避免多次运用LOGISTIC 过程来比较不同自变量对模型的贡献,但是,自动选择法仅仅是依据统 计标准操作的,所以,最好将其看作是变量筛选或者建立模型的初步步 骤,主要用于探测性分析,因为计算机程序可能选到完全无关甚至有悖 商业意义的自变量,变量的选择、评价和模型的最终建立应该由分析人 员负责,而不能完全依赖于计算机。 例16.4:运用LOGISTIC过程的向前选择方法拟合银行不良贷款数 据,数据保存在数据集ex.loan中,在拟合过程中注意保持模型的层级结 构。模型的因变量是BAD,考虑的自变量包括连续性变量DELINQ、 DEBTINC和YROPEN,分类变量为EDUCATION、REASON、 PLOAN,并且要考虑DELINQ和DEBTINC的互动项,以及DEBTINC和 EDUCATION的互动项。 示例代码如下: proc logistic data=ex.loan; class EDUCATION(ref="college") REASON(ref="car") PLOAN(ref="0")/param=reference; model BAD(event="1") = DELINQ DEBTINC YROPEN EDUCATION REASON PlOAN DELINQ* DEBTINC DEBTINC*EDUCATION /stb parmlabel selection=forward hierarchy=single details; title "Forward Selection"; run; 该代码的部分输出如图16.25所示。 由于选项SELECTION=FORWARD,因此第0步时,拟合的模型中 只有截距项,残差的卡方检验比较了由全部自变量和两个互动项拟合的 全模型与当前模型。残差卡方检验的原假设是全模型和当前模型没有显 著性差别,但这里的p值小于0.0001,因此,拒绝原假设,说明全模型 和当前模型具有显著差别,也说明了,在全部的自变量和互动项中存在 着有价值的项。 因为MODEL语句中使用了选项DETAIL,因此也输出了每一步中尚 未进入模型的自变量的显著性检验结果,如图16.26所示。这里,变量 DELINQ、DEBTINC、REASON和PLOAN都是显著的。下一步,系统 将会从这4个变量中选择一个p值最小的变量进入模型。 图16.25 例16.4中向前选择法第0步模型输出 图16.26 例16.4中向前选择法第0步尚未进入模型的自变量显著性检验 第一步时,变量Ploan进入模型,随后输出了模型拟合的效果及参 数估计和假设检验结果,并且对只含截距项和变量Ploan的模型和全模 型进行了比较,如图16.27所示。得知当前模型和全模型仍然具有显著 差异,也就是说,还有剩余的变量和互动项中还存在有价值的项。图 16.28中输出了第1步中尚未进入模型的自变量的显著性检验结果。 (中间省略部分输出结果) 图16.27 例16.4中向前选择法第1步模型输出 图16-27 (续) 图16.28 例16.4中向前选择法第1步尚未进入模型的自变量显著性检验 在变量YROPEN进入模型之后,系统重新拟合了模型,如图16.29 所示。残差的卡方检验显示当前模型和全模型之间没有显著差异,如图 16.30所示。此时,只剩下变量Education没有进入模型,并且该变量的p 值为0.5242,不显著,如图16.30所示。因此,系统输出如图16.31所示 的信息。 图16.29 例16.4中向前选择法第6步模型输出 图16-29 (续) 图16.30 例16.4中向前选择法第6步残差检验和尚未进入模型自变量检 验 图16.31 例16.4中向前选择法剩余的自变量中再无显著自变量 在最后,系统对向前选择过程中依次进入模型的变量进行了简单的 汇总,如图16.32所示。 图16.32 例16.4中进入模型的变量汇总信息 最终的模型中包含了变量Ploan、Reason、DEBTINC、DELINQ和 YROPEN,以及互动项DELINQ*DEBTINC。 16.5 本章小结 本章首先介绍了LOGISTIC回归模型的推导过程、假设条件和参数 估计的基本原理。然后介绍了用SAS的LOGISTIC过程拟合LOGISTIC回 归模型的基本用法,随后结合示例,引出了发生比和发生比率的概念, 并讨论了LOGISTIC回归模型中的参数与发生比率的关系,以及如何评 价LOGISTIC模型拟合优度和预测准确性。在此基础上,介绍了如何运 用LOGISTIC过程中其他语句分析发生比率。最后,讨论了如何探索自 变量与Logit值的关系、自变量之间的互动作用,以及运用LOGISTIC过 程进行模型选择的方法。 第17章 时间序列分析 在经济学、工程学、自然科学(特别是地球物理学和气象学)和社 会科学等领域,被研究的对象在其发展过程中,由于受到各种偶然因素 的影响,往往表现出某种随机性,它们常常被记录成一系列随时间而变 化的数据序列。我们把按时间顺序生成的、等时间间隔的这种数据序列 称为时间序列。 很多数据都是以时间序列的形式出现的,比如:某种产品的月度需 求量、公路事故的周度数量、某化工过程每小时的产出量,等等。时间 序列的一个本质特征就是相邻观测值之间具有相互依赖性,这种依赖特 征具有很大的实用价值。时间序列分析就是对这种依赖性进行分析的技 术。 本章中将讨论如何建立、识别、拟合和检验时间序列模型,并结合 SAS软件中的TIMESERIES过程、ARIMA过程、FORECAST过程、 AUTOREG过程及ESM过程,介绍如何对平稳时间序列和非平稳时间序 列进行建模和预测。 17.1 时间序列基本概念 为了便于后面具体时间序列分析方法的介绍,本节中先介绍基本概 念。 17.1.1 了解时间序列 如果一个时间序列中的时间是连续的,那么该时间序列就是连续 的。如果时间是离散的,那么该时间序列就是离散的。因此,一个离散 时间序列在时刻t1,t2,…,tN得到的观测值就可以记为y(t1), y(t2),…,y(tN)。在本章中,仅仅考虑离散的时间序列。注意, 时间序列中的观测值是按照固定的时间间隔取得的,时间间隔可以是 秒、分钟、小时、天等。 如果一个时间序列的取值取决于某些数学函数,如 则称该时 间序列是确定性的。如果一个时间序列的未来值只能用概率分布的形式 来描述,则表示该时间序列不具有确定性,称为统计时间序列。时间序 列图,也称为时序图,就是一个二维坐标图,横坐标表示时间,纵坐标 表示序列取值。时序图可以直观地帮助我们了解时间序列的一些基本分 布特征。如图17.1所示就是一个时序图,该图展示了从1990年1月到 2013年12月美国国内航线旅客数量(源数据来自 http://www.transtats.bts.gov网站),每个圆圈代表着一个观测值。尽管 在这个序列中有明确的上下波动形状,但是要想准确地预测下一个时段 的值仍然是不可能的。这就是本章所关注的统计时间序列。 图17.1 从1990年1月至2013年12月美国国内航线旅客数量 如果要考察美国国内航线的旅客数量,很明显是要考虑时间t的, 所以可以把旅客数量表示为Y(t)。对每一个确定的时间t0,Y(t0)都 是一个随机变量。理论上t的取值范围是(-∞,+∞),Y(t)为无穷多 个依赖于时间t的随机变量,我们称之为随机过程。 对随机过程Y(t)的值进行一次观测和记录,就可以得到在本章开 始时所提到的一系列随时间而变化的数据序列,实际上该序列已经是一 个确定(而非随机的)常规意义的函数Y(t),我们称之为随机过程 Y(t)的一个现实。当随机过程Y(t)的现实的时间参数为离散的,并 且时间取值的间隔相等时,那么该现实就是一个时间序列。在美国国内 航线旅客数量的示例中,如果我们仅仅关心时间t为月份的情况,我们 所记录到的X(t)在1990年1月到2013年12月的一系列值,就是一个表 示美国国内航线每个月份旅客数量的时间序列。 时间序列分析的目的是选择恰当的技术和方法,建立合适的随机过 程模型,由时间序列的当前值和过去值对未来值进行预测,并解释和描 述外部因素和异常干扰对于时间序列的影响,进而通过设计有效的控制 方法对时间序列进行控制。 17.1.2 时间序列的数字特征 时间序列分析方法是根据时间序列观测间的依赖性特点来建立模 型,所以对该依赖性特征的识别很重要。用图形的方法可以在一定程度 上识别时间序列的特征,且很直观。来看几组时序图,如图17.2~图17.4 所示。 图17.2 时间序列1-因特尔公司股票月度成交量 图17.3 时间序列2-国内原油月度产量 这些图形的特点如下。 ·时间序列1:该时间序列变化平稳,无明显的周期特征,无明显的 趋势。 ·时间序列2:该时间序列有明显的增长趋势。 ·时间序列3:该时间序列变化平稳,但有明显的周期性特征。 图17.4 时间序列3-北京月度平均气温 但是图形识别不是量化的标准,所以往往不够准确。 时间序列的数字特征是时间序列的重要统计特征,也是量化识别时 间序列的重要依据。假设{Yn}是一个时间序列,可以考察它所具有的数 字特征有哪些。 ·均值函数: Fn(y)是Yn的分布函数。 ·方差函数: ·自协方差函数: 也记为γk,方差函数是 自协方差函数的特殊情况。 ·自相关函数: 自相关函数描述了时间序列中不同观测之 间的线性相关程度,也就是依赖性程度,也记为ρk。 需要注意的是,虽然μn,γ(n,n+k)和ρ(n,n+k)被Yn的分布唯 一决定,但是反过来,一般情况下,并不能由μn、γ(n,n+k)和 ρ(n,n+k)唯一确定Yn的分布。也就是说,具有不同分布的时间序列 可以有相同的均值函数、自协方差函数和自相关函数。但对于大量的实 际应用而言,通过以上数字特征来掌握时间序列的统计特性已经足够 了。时间序列分析正是通过分析时间序列的数字特征来分析时间序列的 行为和特点的。 满足以下条件的时间序列称为平稳时间序列: ·EYn=μ,(与n无关),对于 n∈T ·ρ(n,n+k)=ρ(k)(仅与时间间隔k有关),对于 n,k∈T 换句话说,平稳时间序列的均值是常数,方差也是常数,序列没有 明显的变化趋势,观测值始终围绕在同一个水平线上下波动。图17.2符 合平稳时间序列的特征,图17.3是非平稳时间序列。 满足以下条件的时间序列称为白噪声序列,也称为纯随机序列: ·EYn=0, n∈T ·p(n,n+k) 从定义来看,白噪声序列是平稳时间序列中的特例。由于白噪声序 列不同时刻的取值相互独立(从自相关函数判断),因此从已知的观测 值不能对未来进行推断和预测,所以白噪声序列不能用来建立模型。图 17.5是一个白噪声序列的时序图。 图17.5 白噪声序列 平稳时间序列和白噪声序列在后面的讨论中将经常出现。 在实际应用中,由于信息的缺乏,我们往往不可能知道时间序列理 论均值和自相关函数,但是通过样本可以计算样本的均值和样本的自相 关函数。 假设有一组时间序列的观测值为y1,y2,…,yN,其中N称为样本 长度,则: 时间序列的样本均值为 时间序列的样本自相关函数为 特别地,当k=0时, 其中, 也就是说, 样本自相关函数可以作为时间序列自相关函数的一个估计。 17.1.3 常见平稳和非平稳模型 前面说过,时间序列是随机过程的一个现实。我们对时间序列进行 分析,就是希望根据时间序列的特征,找到可以刻画这个时间序列的随 机过程。 接下来,将介绍描述时间序列的一类重要随机模型:平稳模型。平 稳模型会假设随机过程保持概率特征上的统计均衡,其均值和方差不随 时间而改变,即变化都是在一个固定的均值水平上,具有固定的方差。 这类模型非常适合用来对平稳时间序列进行刻画。 通常,我们用yt,yt-1,yt-2,…来表示等间隔时间t,t-1,t-2,…上 的序列值,用 来表示关于μ的序列偏差,其中μ为时间序列的均 值。 1.算子 为了便于对平稳模型和非平稳模型进行介绍,首先引入后移算子 B,后移算子B定义为Byt=yt-1,从而有Bnyt=yt-n。其逆运算由前移算子 F=B-1通过Fyt=yt+1给出,从而Fnyt=yt+n。另一个重要的算子是向后差分 算子,它可用▽表示,定义为▽yt=yt-yt-1,如果用B的表达式来表示, 则有▽yt=yt-Byt=(1-B)yt。 2.自回归模型 在描述某类实际中出现的时间序列时,一种特别有用的随机模型是 自回归模型。在该模型中,随机过程的当前值被表达为由有限的过程先 前值的线性组合和一个干扰(白噪声)εt构成,其形式如下: 该等式表示的随机过程称为p阶自回归过程,简称AR(p)过程。 其中, ,μ为时间序列的均值。称其为自回归是因为这是从线性回 归中发展而来的,但是,这里不再是用x预测y,而是用y自身的历史值 来预测y,也称AR模型。如果引入后移算子B,那么由上面的等式可以 很容易地推导出p阶自回归算子为: φ(B)=1-φ1B-φ2B2-…-φpBp 从而,p阶AR模型就可以简记为: 该模型中包含了p+2个未知参数μ、φ1、φ2、…、φp、σ2ε,在实际 中,这些参数必须由数据来估计,φ1、φ2、…、φp称为自回归参数,参 数σ2ε为白噪声过程εt的方差。 需要特别说明的是,1阶AR模型记为AR(1),也可以表示为: yt=φ0+φ1yt-1+εt 其中,φ0=μ(1-φ1)。 在上面的自回归模型中,可以将 代入 中消去 采用类似方法可以继续代换 等,最后得到一个ε的无限加权和。 考虑简单的1阶AR模型,经过m次替换后,即令 在右 边可以得到: 当m→∞时,将得到无穷级数 在一般的AR模型中,有 这等价于: (1)自相关函数 对于AR(p)模型得: 取数学期望,当k>0时,期望 两边同乘 为0,因为 只涉及直到t-k时 刻的干扰,故与εt不相关。所以可得如下差分方程: 上式两边同除以γ0,则得到自相关函数满足同样的差分方程: 因此,ρk的通解可以表示为ρk=A1Gk1+A2Gk2+…ApGkp,其中G-11, G-12,…,G-1p是特征方程φ(B)=1-φ1B-φ2B2-…-φpBp=0的根。 为了AR过程的平稳性,要求|Gi|<1。 由特征方程φ(B)=0的根的性质可知,一个平稳的自回归过程的 自相关函数的变化趋势是由指数衰减和正弦波振荡衰减构成的。这是我 们根据自相关函数判断序列平稳性的理论基础。 (2)自回归参数的表示 令k=1,2,…,p,对于AR(p)模型,两边同乘 ,并取数学期 望,且同时除以γ0,可以得到以下线性方程组: ρ1=φ1ρ0+φ2ρ1+…+φpρp-1 ρ2=φ1ρ1+φ2ρ0+…+φpρp-2 … ρp=φ1ρk-1+φ2ρk-2+…+φpρ0 该线性方程组通常称为Yule-Walker方程,其中ρ0=1。用样本自相关 系数 代替ρk,就得到了自回归参数φ1,φ2,…,φp的Yule-Walker估 计。如果我们记 则参数解φ可以表示为φ=P-1pρp。 (3)偏自相关函数 在分析一个时间序列时,最初我们可能不知道适合于观测序列的 AR过程的具体阶数,即p的取值,要采用类似于在多元回归中的方法去 确定自变量的个数。其实,这时可以参考偏自相关函数。 偏自相关函数是基于以下事实的一种描述手段:只要一个AR(p) 过程具有无限延伸的自相关函数,那么,就可有自相关函数的p个非零 函数来描述自身的特性。若用φkj表示k阶差分方程回归表达式式中的第j 个系数,φkk就是最后一个系数。由此可得到Yule-Walker方程,记为: 对k=1,2,3,…,依次求解方程,可以得到: 对于任何平稳过程,都可以由上述Yule-Walker方程定义偏自相关 函数φkk,当然也都是作为过程自相关函数ρk的函数。并且,对于 AR(p)过程,对于所有的k>p,有φkk=0,该特征均适合于描述p阶AR 过程。 φkk之所以可称为时间序列{yt}延迟为k的偏自相关函数,这是因为 φkk实际上等于yt和yt-k之间扣除了yt-1、yt-2、…、yt-k-1的影响之后的相关 函数,也可以认为是yt和yt-k之间未被yt-1、yt-2、…、yt-k-1所解释的相关 关系。 因此,在求偏自相关函数的估计时,可以顺次拟合阶数为1、2、 3…的自回归方程,在每阶段的拟合中挑出最后一个系数,得到估计 Quenouille证明了:在p阶AR过程的假设之下,阶数大于或者等于 p+1的偏自相关估计值近似服从均值为0、方差为1/n的独立正态分布, 其中n为观测个数。这一论断可以作为判断AR模型阶数p的取值依据之 一。 3.移动平均模型 自回归模型是将序列的偏差 表示为p个过去值 的有限 加权和,再加上一个白噪声。利用上面的推导方法在自回归模型中将 等代换掉,可以将 表示为ε的无限加权和。 假若 线性依赖于有限的q个ε的过去值,如 该等式称为q阶移动平均过程(MA),是另一类有重要实践意义的 模型。“移动平均”一词可能会导致某些误解,因为乘在ε上的权数1,θ1,-θ2,…,-θq不必总和为1,也不必是正数。但是这一专业术语已被 广泛使用,因此我们也采用这个称谓。如果定义一个q阶移动平均算子 为 θ(B)=1-θ1B-θ2B2-…-θqBq 则移动平均模型可以简记为 考虑到 =yt-μ,表明该模型中包含了q+2个未知参数μ、θ1、θ2、 …、θp、σ2ε,其中θ1、θ2、…、θp称为移动平均参数,在实际应用中必 须由数据来估计。 (1)自相关函数 根据 =εt-θ1εt-1-θ2εt-2-…-θqεt-q,以及εt为白噪声的事实,MA(q) 的自相关函数为 γk=E{(εt-θ1εt-1-θ2εt-2-…-θqεt-q)(εt-k-θ1εt-k-1-θ2εt-k-2-…-θqεt-k-q)} =-θkE[ε2t-k]+θ1θk+1E[ε2t-k-1]+…+θq-kθqE[ε2t-q] 因为εt是不相关的,并且对于k>q,有γk=0,因此该过程的方差为 可以看到,对于MA(q)的过程,当延迟阶数超过q时,过程的自 相关函数为0。换言之,移动平均过程的自相关函数具有超出q步延迟的 截尾性。 (2)移动平均参数的表示 由上述自相关函数的推导结果可知,若ρ1、ρ2、…、ρq为已知,则 由q个方程就可以解出参数θ1、θ2、…、θq。然而,与自回归过程线性的 Yule-Wallker方程不同,这里的q个方程为非线性方程。除了q=1的简单 情形,其余情形只能用迭代法求解。 (3)偏自相关函数 对于一阶的MA过程,由Yull-Walker方程及 过计算可以得出 并且ρk=0,k>1,通 可见偏自相关函数被衰减指数所控制。 对于高阶的MA过程,偏自相关函数的严格表达式是很复杂的,但 是可以证明偏自相关函数被衰减指数和(或)衰减正弦波控制。 (4)自回归过程和移动平均过程的对偶性 平稳的AR过程具有在某阶之后全为零的偏自相关函数,但是它的 自相关函数是无限延伸的,且由衰减指数或衰减正弦波混合生成;相 反,有限的MA过程具有在某阶之后全为零的自相关函数,但由于它等 价于一个无限的AR过程,因此其偏自相关函数将无限延伸,且被衰减 指数和(或)衰减正弦波控制。 4.自回归移动平均模型 为了在实际拟合时间序列时有更大的灵活性,有时会将自回归项和 移动平均项一起纳入模型,这就引出了自回归移动平均模型 (ARMA)。 也称为(p,q)阶的自回归移动平均模型,简记为ARMA(p,q) 模型,模型中有p+q+2个未知参数μ、φ1、φ2、…、φp、θ1、θ2、…、 θp、σ2ε,需要根据实际数据来估计。在实际中,为了描述一个实际发生 的平稳时间序列,往往能够得到自回归、移动平均,或者二者混合的模 型,其中p和q的值不大于2,或者常常是小于2的。 表17.1是针对自回归、移动平均及混合ARMA过程性质的总结。 表17.1 自回归、移动平均及混合ARMA过程性质总结 5.非平稳模型 在工商业中,常会遇到许多序列(如股票价格)都表现出了非平稳 的特性,比如说不围绕一个固定的均值变化。然而这样的序列可能会表 现出某种相同的特征,具体来说,虽然这些波动的总体水平在不同时间 里其表现是不同的,但是在允许水平有差异的前提下,这些序列的其他 广义特征可能是相似的。有些情况下,某种序列在经过一阶或者多阶向 后差分后可以转换为平稳时间序列。令ωt=(1-B)dyt=▽dyt,若ωt是平 稳的,则有 φ(B)ωt=θ(B)εt 也就是说,描述具有该种同质非平稳特征的模型可以表示为 φ(B)(1-B)dyt=θ(B)εt 在实际应用中,d通常是0或1,或者最多是2。当d=0时与平稳特征 相一致。 这样,由φ(B)(1-B)dyt=θ(B)εt所定义的过程为描述平稳或非 平稳的时间序列提供了一个有效的模型,称为(p,d,q)阶自回归求 和移动平均过程(ARIMA),该过程定义为 ωt=φ1ωt-1+φ2ωt-2+…+φpωt-p+εt-θ1εt-1-θ2εt-2-…-θqεt-q′ 其中,ωt=▽dyt。如果d=0时,用yt-i-μ代替ωt-i,i=0,…,p,则该 模型包括了平稳的混合模型(ARMA模型);而纯自回归模型(AR模 型)和纯移动平均模型(MA模型)就是该模型的特例。 在自回归求和移动平均过程(ARIMA)这个名词中包含了“求 和”一词,原因是:由ωt=(1-B)dyt,可得yt=((1-B)-1)dωt,由 Tylor展式可知(1-B)-1=1+B+B2+…,则 (1-B)-2ωt=(1-B)-1ωt+(1-B)-1ωt-1+(1-B)-1ωt-2+(1-B)-1ωt2 3+…=(1+2B+3B +…)ωt 对于高阶d也可以定义同样的计算。因此,一般的自回归求和移动 平均过程(ARIMA)就是对平稳过程ωt作d次求和而生成的。后面将描 述如何使用上述模型的一个具体形式来表达非平稳时间序列。 17.1.4 SAS时间序列分析软件简介 在SAS系统中,有四大模块可以用来进行时间序列分析。 ·BASE SAS:利用第一篇中介绍的基本编程语言和时间序列的理论 知识进行建模和预测。 ·SAS/STAT:运用最小二乘法对历史数据或者残差进行回归。 ·SAS/ETS:是SAS系统中专门进行时间序列预测的模块,提供了多 种PROC步,对带有时间标识的序列进行处理、建模和预测。本章主要 介绍运用该模块中的PROC步进行建模。 ·SAS Forecast Server:是一种具有可扩展性的大型自动化预测解决 方案,具有友好的图形用户界面,如图17.6所示。在较少人工干预的情 况下,可快速自动生成大量高质量的统计模型,能自动选择最合适的统 计模型,并生成预测。 17.2 平稳时间序列分析 平稳时间序列是时间序列中一类重要的时间序列,对于该时间序 列,有一套非常成熟的平稳序列建模方法,这也是本节中将重点介绍的 部分。对于非平稳序列,可以通过差分、提取确定性成分等方法,将其 转化成平稳序列,再运用平稳序列建模方法进行建模。 在实际操作中,由于样本数据的匮乏,要根据样本数据要找到生成 样本的真实随机过程基本是不太可能的。理论研究表明,任意平稳时间 序列都可以由ARMA过程(包括AR过程、MA过程和混合过程)近似表 示,并且通过ARMA模型可以对序列作出比较精确的预测。 Box-Jenkins建模方法是关于如何分析平稳时间序列、建立ARMA模 型以及进行预测的方法,它也是目前比较流行的一种建模方法。建模过 程基本可以分为如下3步。 1)模型识别:考察时间序列特征,进行模型识别,辨识出有价值 且参数简约的模型子类,如AR(3)、ARMA(2,2)等。 2)参数估计和诊断检验:对已辨识出的模型子类进行数据拟合和 参数估计,在恰当的条件下,有效地运用样本数据对模型参数进行推断 和估计,并对模型进行诊断检验,通过检验拟合模型与数据的关系来揭 示模型的不当之处,从而对模型进行改进。模型识别、参数估计和诊断 检验是不断循环和改进的过程,通过该过程来找到合适的模型表达式。 图17.6 SAS Forecast Server的用户界面 3)预测:利用拟合好的时间序列模型来推断序列其他的统计性质 或预测序列将来的发展。 通常要求,用来建模的观测值的个数至少有50个,最好是100个或 更多。当无法获得50个或者更多的历史观测时,例如进行某种新产品的 需求预测时,可以使用经验或者类似产品的历史需求信息得到一个初始 模型;当获得更多的数据时,这个模型可以随时被更新。 在进行建模时,应使用包含尽可能少的参数的模型,以获得数学上 的充分表达。这就是参数使用的简约性原则,该原则在实际应用中是非 常重要的。如果模型不恰当,或参数使用冗余,将会使预测出现严重问 题。因此,在选择模型时,谨慎和反复试探是非常必要的,这也是一个 不断改进、修正错误和试验的过程。 广义的Box-Jenkins建模方法也可以建立带趋势和季节因素的时间序 列,并且可以根据需要在模型中添加其他输入变量。 17.2.1 数据准备 在实际应用中,我们得到的数据往往是一些交易数据,没有固定的 时间间隔,这些数据很多情况下不能直接用来进行时间序列分析,需要 先对数据进行预处理,例如将交易数据转化成固定间隔的时间序列,进 行数据转换,补全缺失数据,检查是否有异常值等。 数据的预处理是时间序列分析的重要组成部分,会直接影响预测的 准确度。因此,在进行股票每天交易量预测的时候,首先需要将股票交 易数据转化成按天交易的数据;在对汽车的月度需求进行预测时,需要 将汽车按天的销售量累积到按月计算的销售量,等等。这些处理都是为 了保证预测足够准确。 TIMESERIES过程和EXPAND过程主要用来整理时间序列数据资 料,比如说将短时间间隔数据(每天的交易数量)汇总成长时间间隔数 据(月度交易数量),或者将长时间间隔数据(季度销售总量)拆分成 短时间间隔数据(月度销售总量),或者自定义时间间隔生成新的时间 序列。 运用TIMESERIES过程将短时间间隔数据转换成长时间间隔数据的 基本语法如下: PROC TIMESERIES DATA=数据集OUT=输出数据集选项; BY 分类变量; ID 时间变量INTERVAL=时间间隔 ACCUMULATE=累积方法 SETMISSING=缺失值处理方法 START=起始时间END=终止时间; VAR 分析变量1 <分析变量2 ...>; RUN; 其中: ·ID语句指定时间变量,时间变量是指包含时间数据的变量。在使 用TIMESERIES过程之前,数据集必须按照分类变量和时间变量排序。 ·选项INTERVAL=指定输出数据集的时间间隔,取值可以为DAY、 WEEK、MONTH、QUARTER、YEAR等SAS日期间隔取值或者SAS时 间间隔取值。 ·选项ACCUMULATE=指定了累积方法,取值有TOTAL、 AVERAGE、MEDIAN、FIRST、LAST等。 ·选项SETMISSING=指定缺失值处理方法,取值有MISSING、0、 AVERAGE、MEDIAN、FIRST、LAST等,SETMISSING=的默认值是 MISSING。 ·选项START=和选项END=指定时间变量的起始时间和终止时间, 过程步只处理和输出落在这一时间段的观测。 通过TIMESERIES过程得到的输出数据集,是固定时间间隔的序 列,可以被用来进行时间序列分析和建模。 例17.1:数据集ex.stockdaily中包含了某上市公司从1995年9月到 2011年7月之间每天股票的价格和交易量信息,具体如下。 Date:交易日期 Open:开盘价 High:最高价 Low:最低价 Close:收盘价 Volume:交易量 LogVolome:交易量的Log形式,LogVolume=Log(Volume) Year:交易年份 Month:交易月份 部分数据如图17.7所示。 图17.7 数据集ex.stockdaily部分内容 该数据是按天统计的股票信息数据,现在要用TIMESERIES过程, 将1995年10月1日到2011年2月28日之间按天统计的数据转换成月度数 据。代码如下: proc timeseries data=ex.stockdaily out=ex.stockmonthly; id date interval=month accumulate=average setmissing=missing start="01Oct1995"d end="28Feb2011"d; var volume; run; 在上述程序中,需要汇总的变量是volume,INTERVAL=MONTH 指定输出数据集中数据的时间间隔为月,每天交易量的平均值将作为汇 总后的数据存储在变量volume中。在原始数据集ex.stockdaily中,如果 在1995年10月1日到2011年2月28日间,缺少某个月份的数据,那么在输 出数据集中,将自动生成一条该月份的数据,并且volume的取值为缺失 (取值由选项SETMISSING=MISSING指定)。 转换后的数据如图17.8所示。 图17.8 数据集ex.stockmonthly部分内容 在数据准备的过程中,另一个重要的过程步是EXPAND过程。 EXPAND过程用来把时间序列从一种采样间隔或频率转换为另外一种, 并且补插这一时间序列中的缺失值。使用EXPAND过程可以把高频率间 隔的时间序列数据转换为较低频率间隔,反之亦然。例如,可以把以季 度为频率间隔的时间序列处理(如汇总、取均值或期末值等)为以年度 为频率间隔的时间序列,也可以在一个以年度为频率间隔的序列中补插 季度估计,得到以季度为频率间隔的时间序列。EXPAND过程的基本语 法如下: PROC EXPAND DATA=数据集OUT=输出数据集FROM=输入数据集时间间隔TO=输出数据集时间间隔 <其他选项>; BY 分类变量; CONVERT分析变量1 <分析变量2 ...>/选项; ID 时间变量; RUN; 其中: ·和TIMESERIES过程一样,在使用EXPAND过程时,输入数据集必 须已经按照分类变量和时间变量排过序。 ·在PROC EXPAND语句中,选项FROM=指定输入数据集时间间 隔,选项TO=指定输出数据集时间间隔,默认情况下,选项TO=的取值 为输入数据集的时间间隔。当选项FROM=DAY,TO=DAY时,如果输 入数据集中,缺少某一天的数据,那么在输出数据集中,将自动生成一 条新的数据,并且在日志中,会生成一条警告信息。 ·选项METHOD=可以指定插值方法,取值有SPLINE、JOIN、 STEP、AGGREGATE和NONE,当取值为NONE时,表示不进行插值。 PROC EXPAND语句和CONVERT语句中都可以使用选项METHOD,但 是CONVERT语句中选项METHOD的优先级更高。 例17.2:ex.stockdaily数据集中的股票数据是按天收集的,但是因为 一些原因,使得该数据集中并没有包含所有交易日的数据,也就是说, 某些交易日的数据被遗漏掉了。要使得分析的序列是固定时间间隔的, 可以运用EXPAND过程对该序列进行预处理。 示例代码如下: proc expand data=ex.stockdaily out=work.stockdaily from=weekday align=beginning method=none; id date; convert volume; convert logvolume; run; 部分日志信息如图17.9所示。 图17.9 例17.2日志中警告信息 该日志显示在输入数据集中,1995年11月22日和11月24日之间缺少 一条数据,在输出数据集中,将自动生成一条新的观测,Date的取值为 1995年11月23日。由于选项METHOD=NONE,因此,新观测的Volume 和Logvolume的取值为缺失,如图17.10所示。 图17.10 例17.2日志信息 原始数据集ex.stockdaily中包含4000条数据,经过EXPAND过程处 理后,增加了部分数据,变为了4130条数据。 EXPAND过程的用法非常灵活,还可以将某种固定间隔的时间序列 转换成其他任意指定时间间隔的数据(查看SAS帮助文档中选项 FACTOR的用法),并且可通过指定的方法补插时间序列中的缺失值。 17.2.2 平稳性和白噪声检验 1.平稳性的图检验 拿到一个时间序列之后,首先判断它的平稳性。判断一个序列是否 平稳有两种检验方法,一种是图检验方法,即根据时序图和自相关系数 图显示的特征做出判断;一种是单位根检验法,即构造检验统计量进行 假设检验的方法。 图检验方法是一种操作简便、运用广泛的平稳性判别方法,但是判 别结论容易带有一定的主观性,所以最好能辅以统计检验方法进行判 断。 根据平稳时间序列均值和方差为常数的性质可知,平稳时间序列的 时序图应该显示出该序列始终在一个常数值附近随机波动,而且波动的 范围有明显的相似性特点。如果时序图显示出该序列有明显的趋势性或 者周期性,那么它通常不是平稳序列。根据这个性质,很多非平稳序列 通过查看时序图就可以被识别出来。 我们知道,自相关函数是用来描述时间序列中不同观测之间的线性 相关程度的,可以证明平稳时间序列通常都具有短期相关性,具体描述 就是随着延迟期数k的增加,平稳序列的自相关系数 会很快衰减为0。 反之,非平稳序列的自相关系数 衰减为0的速度通常比较慢。这就是 我们利用自相关图进行平稳性判断的标准。自相关图,也称ACF图,全 称为Autocorrelation Function Plot,横坐标表示延迟期数(也称滞后期 数),纵坐标表示自相关系数的取值,图中每一个柱子都代表了某延迟 期数对应的自相关系数的取值。观察以下两组序列的时序图和ACF图 (如图17.11和图17.12所示),可以明显看出,图17.11中的序列随着时 间的变化有明显的上升趋势,是非平稳的,因此自相关系数衰减的速度 很慢,而图17.12中平稳序列的自相关系数则衰减很快。 图17.11 图17.12 非平稳时间序列序列图和ACF图 平稳时间序列序列图和ACF图 另一种常见的平稳性检验方法是单位根检验(Unit Root Test),在 后面介绍趋势时间序列时将重点讲解。 2.白噪声检验 并不是所有的平稳序列都值得建立模型,只有那些序列值之间具有 相互依赖性,历史数据对未来的发展有一定影响的序列,才值得建模, 建模是为了预测序列未来的发展。如果序列值彼此之间没有任何相关 性,如白噪声序列,过去的行为对将来的发展没有丝毫影响,从统计分 析的角度而言,是没有任何分析建模的价值的。 为了判断某个序列是否值得继续分析建模,需要对其进行白噪声检 验。由白噪声序列的定义知,对于任意k期延迟,都有自相关系数 ρk=0,k>0。需要指出的是,这是理想的状况,实际上,由于样本序列 的有限性,会导致白噪声序列的样本自相关系数 不会绝对为0。 随机产生一个服从标准正态分布的白噪声序列,然后观察它的时序 图和自相关系数图,如图17.13和图17.14所示。 在图17.14中可以看到,这个白噪声序列的大部分样本自相关系数 都不等于0,但是这些自相关系数都非常小,都在0值附近以一个很小的 幅度做随机波动。 Barlett证明,如果一个时间序列是白噪声序列,样本长度为n,那 么该序列的非0期延迟期数的样本自相关系数将近似服从均值为0、方差 为样本长度倒数的正态分布,即: 既然样本自相关系数的分布具有这样的性质,那么就可以构造统计 量从统计意义上来检验时间序列是否为白噪声序列了。 图17.13 随机产生的服从标准正态分布的白噪声序列的时序图 图17.14 白噪声序列的自相关系数图 这里,我们不去单独地考虑每一个自相关系数ρk,而是将前m个自 相关系数作为一个整体来考虑,通过它们构造一项指标来判断序列是否 是白噪声序列。由于序列取值之间的变异性是必然的,而相关性是偶然 的,因此一定要有足够的证据才能证明序列之间存在相关性。若进行白 噪声检验的原假设和备择假设分别为: H0:ρ1=ρ2=…=ρm=0, H1:对于 即序列为白噪声序列 且k≠0,使得ρk≠0,即序列不是白噪声序列 为了检验这个原假设,Box和Ljung推出了LB(Ljung-Box)统计 量: 其中,n为样本长度,m为指定延迟期数, 是样本自相关系数。如 果序列是白噪声序列,根据正态分布和卡方分布的关系,可以证明LB 统计量近似服从自由度为m的卡方分布;反之,如果序列不是白噪声序 列,则LB的取值会陡增。若给定显著性水平α,可以计算出卡方分布在 1-α处的分位数χ1-α,如果由样本序列计算出来的LB统计量大于χ1-α,则 说明有充足的理由拒绝原假设,也就是说, 使得ρk≠0,即该时间 序列不是白噪声序列。 白噪声检验不仅可以用在对原始时间序列的检验中,也可以用在对 残差序列的检验中。如果模型已经从序列中提取出了所有的有用信息, 那么残差序列应该就是一个白噪声序列,否则的话,说明序列中某种规 律性的信息没有被模型表示出来,也就是说,模型是拟合不足的。 ARIMA过程 SAS中的ARIMA过程是根据Box-Jenkins建模方法开发的一个过程 步,专门用来建立ARIMA模型(包含AR模型、MA模型、混合ARMA 模型和ARIMA模型等)。基本语法如下: PROC ARIMA DATA=数据集选项; IDENTIFY VAR=分析变量<选项>; ESTIMATE <选项>; OUTLIER <选项>; FORECAST OUT=输出数据集<选项>; RUN; 其中: ·IDENTIFY语句用来指定需要分析的时间序列,并计算和输出多种 统计量及相关关系图供用户进行序列分析,识别合适的模型。 ·ESTIMATE语句用来建立模型,为前面IDENTIFY语句中指定的响 应变量拟合ARMA模型或转移函数模型,计算其参数的估计值;并输出 诊断信息,从而判断模型是否不足。 ·OUTLIER语句用来检测ESTIMATE语句中建立的模型没有能够处 理的异常值,在使用该语句前,必须先使用ESTIMATE语句。 ·FORECAST语句根据ESTIMATE语句中计算的参数估计,生成时 间序列的预测值。 ARIMA过程实现步骤和前面介绍的Box-Jenkins建模方法的步骤非 常类似,同样是由模型识别、估计、诊断到预测。在ARIMA过程中有 诸多的选项可实现不同的分析要求,接下来结合例子进行介绍。 例17.3:在例17.2中,已经运用EXPAND过程将原序列转换成了等 间隔的时间序列,接下来,运用ARIMA过程考察ex.stockdaily中的股票 交易量序列(仅考察从2010年1月1日开始的数据),并对序列进行平稳 性检验和白噪声检验。 示例代码如下: proc arima data=work.stockdaily; /*identify the series initially*/ identify var=volume nlag=12; where date>="01Jan2010"d; run; quit; 在上述代码中,IDENTIFY语句使用了选项NLAG=,该选项指定一 个数字告诉系统在计算样本自相关函数、样本偏自相关函数和样本逆自 相关函数时所需考虑的最大延迟期数。为获得一个ARIMA(p,d,q) 模型的初步估计,NLAG=的取值最小必须为p+d+q。数据集中观测的个 数必须大于等于NLAG=的取值。NLAG=的默认值为24和观测个数的1/4 这两个数字中较小的一个。 图17.15中展示的表是ARIMA过程输出的第一部分:序列的基本统 计量和白噪声检验结果。 图17.15 例17.3序列基本信息和白噪声检验 由于程序中指定了NLAG=12,故白噪声检验的最大延迟期数也为 12。白噪声的自相关检查报表输出了m=6和m=12的检验结果(回归“白 噪声检验”中的m的取值),需要指出的一点是,m的取值为6的倍数。 思考一下,本例中,在进行白噪声的自相关性检验时,只检验了至 多延迟12期的LB统计量是否合适?是否需要进行全部400期的延迟检验 (从2010年1月1日开始总共有400个观测点)? 事实上,因为平稳序列通常具有短期相关性,如果观测值之间存在 显著的相关关系,通常只存在于延迟期数比较短的观测值之间。所以, 如果一个平稳序列短期延迟的观测值之间都不存在显著的相关关系,通 常长期延迟之间就更不会存在显著的相关关系了。另一方面,如果一个 平稳序列显示出显著的短期相关性,那么该序列就一定不是白噪声序 列。因此,这里只检验至多延迟12期的LB统计量已经足够。 ARIMA过程的第二部分输出为时序图、样本自相关系数图 (ACF)、样本偏自相关系数图(PACF)和样本逆自相关系数图 (IACF),如图17.16所示。PACF图和IACF图是相应的以延迟期数为 横坐标、样本偏自相关系数和样本逆自相关系数为纵坐标的柱状图。 图17.16 例17.3中序列的趋势和相关分析 ACF图、PACF图和IACF图在模型识别和参数估计中具有重要作 用,在下一节中将详细介绍。 根据时序图和ACF图,即可进行大致的平稳性检验,从时序图中, 基本可以判断观测值通常是以固定的波动幅度围绕在一个固定水平波动 的;根据ACF图的判断准则,样本自相关系数在2阶延迟之后很快衰减 向0(虽然在10阶延迟附近出现振荡),基本可以判断该序列是一个平 稳时间序列。 综合前面的白噪声检验结果,说明该序列不仅可以视为平稳序列, 而且还蕴含着值得提取的相关信息。 17.2.3 模型识别 接下来根据样本数据对ARMA模型的阶数p和q进行识别。我们的主 要工具是样本自相关函数、样本偏自相关函数和样本逆自相关函数,它 们不仅有助于推测模型的形式,而且可以推导出参数的近似估计。 1.运用自相关函数和偏自相关函数的性质识别 先来回忆一下在前面章节中曾讨论过的内容:有关移动平均、自回 归和混合过程的理论,以及自相关和偏自相关函数的特征行为。 1)p阶自回归过程的自相关函数是拖尾的,而它的偏自相关函数在 p阶延迟之后是截尾的。 2)q阶移动平均过程的自相关函数在延迟q阶之后是截尾的,而它 的偏自相关函数是拖尾的。 3)若自相关函数和偏自相关函数均拖尾,则表明是混合过程。进 一步,对于一个包含p阶自回归和q阶移动平均的混合过程来说,其自相 关函数在q-p阶延迟之后是混合的指数和正弦波衰减。与此相应的,混 合过程的偏自相关函数在q-p阶延迟之后被混合的指数和正弦波衰减所 控制。 但在实践中,依据这些性质为模型定阶是有一定困难的。因为由于 样本的随机性,样本的相关系数不会呈现出理论上的完美截尾情况,比 如,本应截尾的样本自相关系数或偏自相关系数仍然会呈现出小值振荡 的情况。这种现象导致我们必须判断,什么情况下该看作相关系数是截 尾的,什么情况下该看作相关系数是在延迟若干阶之后正常衰减到0值 附近做拖尾波动的呢? 对于较大的延迟,假设在q阶移动平均过程下,我们用样本估计值 代替理论自相关系数,可以根据Bartlett公式计算出样本自相关系数的标 准差: 对于偏自相关函数,和前面讨论的一样,在过程为p阶自回归的假 设中,p+1阶或更高阶偏自相关系数的估计值的标准差是 对于适当大小的n,假设理论自相关系数ρk为0,它的估计值 服从 近似正态分布,对于偏自相关系数有类似的结论。 这些事实可以提供一种非正式的标准,用来指示当延迟超出某特定 值后理论自相关函数和偏自相关函数是否实质上为0。 正态分布有如下性质: ·如果样本自相关系数或偏自相关系数在最初的k阶的取值明显大于 2倍标准差范围,而在k阶之后几乎95%的样本相关系数都落在2倍标准 差的范围以内,形成了小值波动;而且样本自相关系数由较大值衰减到 2倍标准差范围内的过程非常突然,这时,通常视为相关系数截尾,阶 数为k。 ·如果样本自相关系数或偏自相关系数在最初的k阶的取值明显大于 2倍标准差范围,而k阶之后有超出5%的样本相关系数落入2倍标准差范 围之外;或者是由显著非零的相关系数衰减为小值波动的过程比较缓慢 或者非常连续,这时,通常视为相关系数不截尾。 注意 由于自相关函数的估计之间可能高度相关,因此,不可能 指望自相关函数的估计值与理论值十分贴近。特别是,当理论自相关函 数已经衰减了,而自相关函数的估计还可能出现相当大的、明显的波动 和趋势,这种相悖的现象在理论中是没有依据的。在运用自相关函数的 估计作为识别依据时,通常能对大致的特征有相当的把握,至于那些更 精细的特征,它们可能未必代表真实的结果,因此,可能需要引入两个 或更多的模型,以便在建模的估计和检验诊断阶段作进一步的研究。 除了样本自相关系数和样本偏自相关系数之外,样本逆自相关系数 也可以用来帮助模型定阶。样本逆自相关系数和样本偏自相关系数的估 计值符号相反,当样本偏自相关系数的截尾或者拖尾性质难以判断时, 可以参考样本逆自相关系数的截尾或者拖尾性质来作出判断。 一阶、二阶AR过程和MA过程以及简单的混合ARMA过程都是特别 重要的,接下来通过例子具体查看这类过程的相关系数的特征。 例17.4:通过例子分析一阶、二阶AR过程和MA过程以及混合 ARMA过程的时序图、ACF图、PACF图和IACF图特征。 示例代码如下: data work.armaExamples; array Y(8); array LagY(8); array Lag2Y(8); /*---- (1) Initialize all 8 series to mean 0 do i=1 to 8; LagY(i)=0; Lag2Y(i)=0; end; LagError=0; ----*/ Lag2Error=0; /*---- (2) Generate as labeled ----*/ do t=-100 to 1600; Date = intnx('day','31dec2006'd,t); error = normal(1234567); Y(1) = 0.9*LagY(1) + error; * AR(1) ; Y(2) = 0.1*LagY(2) + 0.72*Lag2Y(2) + error; * AR(2) ; Y(3) = 1.8*cos(2*constant('pi')/9)*LagY(3) - 0.81*Lag2Y(3) + error; * AR(2) complex roots; Y(4) = error - 0.9*LagError; * MA(1) ; Y(5) = error - 0.1*LagError -0.72*Lag2Error; * MA(2) ; Y(6) = error - 1.8*cos(2*constant('pi')/9)*LagError + 0.81*Lag2Error; * MA(2) complex roots; Y(7) = 0.9*LagY(7) + error + 0.8*LagError; * ARMA(1,1); Y(8) = error; * white noise; if t>0 then output; Lag2Error=LagError; LagError=error; do i=1 to 8; Lag2Y(i)=LagY(i); LagY(i)=Y(i); end; end; keep t date Y1-Y8; run; 上述程序生成了8个时间序列Y1,Y2,…,Y8。Y1是AR(1)过 程生成的时间序列;Y2和Y3分别是由不同的AR(2)过程生成的时间 序列;Y4是MA(1)过程生成的时间序列;Y5和Y6分别是由不同的 MA(2)过程生成的时间序列;Y7是ARMA(1,1)过程生成的时间 序列;Y8是白噪声序列。接下来运用ARIMA过程输出各个时间序列的 时序图、ACF图、PACF图和IACF图。代码如下: proc arima data=work.armaExamples plots=series(corr); identify var=Y1 nlag=12; identify var=Y2 nlag=12; identify var=Y3 nlag=12; identify var=Y4 nlag=12; identify var=Y5 nlag=12; identify var=Y6 nlag=12; identify var=Y7 nlag=12; identify var=Y8 nlag=12; run; quit; 输出内容如图17.17至图17.24所示(其中省略了序列的基本信息和 白噪声检验结果)。 在图17.17中,Y1是AR(1)过程生成的时间序列,样本自相关系 数是指数衰减的, =φk1,从生成Y1的代码中可以看出φ1=0.9。在PACF 和IACF图中,1阶延迟后,偏自相关系数和逆自相关系数都迅速衰减为 小值波动,且落在了2倍标准差范围内,符合截尾特征。 图17.17 例17.4中序列Y1的趋势和相关分析 如图17.18所示,在Y2的ACF图中,自相关系数呈现出类似指数衰 减的特征,在PACF和IACF图中,2阶延迟后,偏自相关系数和逆自相 关系数都迅速衰减成小值波动,具有截尾特征,符合AR(2)过程的相 关系数的特征。 图17.18 例17.4中序列Y2的趋势和相关分析 如图17.19所示,Y3的PACF图和IACF图的形状和特征与Y2的非常 类似,也都是延迟2阶之后截尾。不同的是,在Y3的ACF图中,自相关 函数呈现出正弦波振荡衰减的特征,符合AR(2)的相关系数特征。 图17.19 例17.4中序列Y3的趋势和相关分析 在图17.20中,Y4是MA(1)过程生成的时间序列,ACF图呈现出 明显的截尾特征,在1阶延迟之后,自相关函数迅速衰减成小值波动, 在PACF和IACF图中,偏自相关函数和逆自相关函数都呈指数衰减,具 有拖尾的特征。 图17.20 例17.4中序列Y4的趋势和相关分析 在图17.21中,Y5是MA(2)过程生成的时间序列,ACF图中,自 相关函数在2阶延迟之后,迅速衰减到小值波动,具有截尾特征;PACF 图和IACF图都具有明显的拖尾特征。 图17.21 例17.4中序列Y5的趋势和相关分析 在图17.22中,Y6是另一种MA(2)过程生成的时间序列,ACF图 中,自相关函数在2阶延迟之后,迅速衰减成小值波动,具有截尾特 征;PACF和IACF图中,偏自相关函数和逆自相关函数呈现正弦波振荡 衰减的特征。 图17.22 例17.4中序列Y6的趋势和相关分析 在图17.23中,Y7的ACF图、PACF图和IACF图都呈现出拖尾的特 征,符合混合模型的相关系数特征,但是仅通过相关系数很难判断混合 模型的阶数。 图17.23 例17.4中序列Y7的趋势和相关分析 在图17.24中,Y8是白噪声序列,任意阶延迟的自相关函数、偏自 相关函数和逆自相关函数都近似为0。 图17.24 例17.4中序列Y8的趋势和相关分析 前面讨论了如何用TIMESERIES过程进行数据的预处理,实际上除 了对数据进行预处理以外,TIMESERIES过程也可以用来生成序列的 ACF图、PACF图和IACF图。语法如下: PROC TIMESERIES DATA=数据集PRINT=(选项)PLOT=(选项); ID 时间变量INTERVAL=时间间隔; VAR 分析变量1 <分析变量2 …>; RUN; TIMESERIES过程默认不输出任何图形或报表,因此在PROC TIMESERIES语句中,需要使用选项PRINT=和PLOT=来输出指定的报 表或图形。当同时指定多个图形或报表时,需要将它们用括号括起来。 可以输出的图形包括SERIES、RESIDULE、HISTOGRAM、CORR、 ACF、PACF、IACF、WN(白噪声概率)、TCC(trend-cycle component)、SC(seasonal component)等;可以输出的报表包括 DECOMP、SEASONS、DESCSTATS、SUMMARY、TRENDS等。 例17.5:利用TIMESERIES过程作出数据集work.armaExamples中序 列Y1的相关系数图。 示例代码如下: proc timeseries data=work.armaExamples plots=(corr acf pacf iacf); var Y1; id date interval=day; run; 输出内容如图17.25和图17.26所示(省略了数据集的基本信息输出 和序列的基本信息输出)。 从图17.25可见,在相关性分析面板里同时输出了Y1的ACF图、 PACF图和IACF图,以及白噪声检验图。前三者和ARIMA过程输出的结 果一样,这里不再解释。 图17.25 例17.5中序列Y1的相关性分析 白噪声检验图以延迟期数为横坐标,以Pr>|延迟期数对应的统计量 LB的取值|的概率为纵坐标,每个柱子代表每个延迟期数对应的概率, 概率越小柱子越高,图中,任意延迟期数对应的概率都小于0.001,代 表着应拒绝白噪声检验的原假设,即Y1不是白噪声序列。 接下来,TIMESERIES过程还分别输出Y1的ACF图及标准化ACF 图、PACF图及标准化PACF图、IACF图及标准化IACF图。这里仅展示 ACF图和标准化ACF图作为示例。标准化的ACF图仍然以延迟期数作为 横坐标,以标准化之后的ACF作为纵坐标,它标出了2倍标准差范围和1 倍标准差范围。 2.自动识别 对于某些序列,通过观察自相关函数、偏自相关函数和逆自相关函 数图,可以判断出AR模型或MA模型的阶数。但是,有的时候,ARMA 混合模型可能可以生成更加准确的预测,并且一个合适的ARMA(p, q)模型所含参数的个数(p+q+2)通常要小于纯粹的AR(p')或者 MA(q')所含参数的个数。但是,从例17.4中的时间序列Y7可知, ARMA混合模型的阶数通过观察相关系数图是很难判断的。 为了更有效和更简便地辨识ARMA模型的阶数,一些其他的模式辨 识方法被提出并应用,例如ESACF(延伸自相关系数法)、SCAN(最 小典型相关法)和MINIC方法(最小信息准则法)。在ARIMA过程 中,IDENTIFY语句中的选项ESACF、MINIC和SCAN就是分别对应的 这3种模式辨识方法的。其使用语法如下: PROC ARIMA DATA=数据集; IDENTIFY VAR=分析变量 ESACF SCAN MINIC P=(Pmin:Pmax) Q=(Qmin:Qmax) RUN; PERROR=(PEmin:PEmax); 其中,选项P=指定了AR阶数范围,选项Q=指定了MA阶数范围; 选项PERROR=指定了用来拟合残差序列的AR模型的阶数范围,默认情 况下,PEmin设定为Pmax,PEmax设定为Pmax和Qmax之和。选项 ESACF、SCAN和MINIC可以分开使用。 图17.26 例17.5中序列Y1的自相关图和标准化自相关图 例17.6:分别用选项ESACF、MINIC、SCAN对work.armaExamples 中的序列Y7进行模型识别。 示例代码如下: /*---- ESACF ----*/ proc arima data=work.armaExamples; identify var=Y7 nlag=12 esacf p=(0:12) q=(0:12) perror=(3:12); run; 输出的报表中包含了ESCAF方法的广义自相关系数矩阵和P值矩 阵,根据这两个矩阵的结果,SAS接着输出了ESACF方法识别出的待选 模型,如图17.27所示。待选模型包括ARMA(1,1),ARMA(9, 8)和ARMA(10,8)。 这里需要解释一下p+d的意思,例如,当p+d=2时,p和d的取值有3 种情况: ·p=2,d=0 ·p=1,d=1 ·p=0,d=2 在前面介绍自回归求和移动平均过程(ARIMA)时曾讲到,如果 序列是非平稳的,可先通过差分将非平稳序列转化成平稳序列,d则表 示差分的阶数。当d>0时,ARMA模型表示非平稳模型,所以上面 ESACF方法也提供了一些待选的非平稳模型。 这里SAS直接输出了根据广义自相关系数矩阵和P值矩阵识别出的 待选模型,如果读者对如何从这些矩阵中分析识别待选模型感兴趣,可 以查看相关参考文献,如Tsay and Tiao(1984),及Pena,Tiao and Tsay(2001)。 下面这段程序的输出了SCAN方法的典型相关估计矩阵和卡方统计 量的P值矩阵。 /*---- SCAN ----*/ proc arima data=work.armaExamples; identify var=Y7 nlag=12 scan p=(0:12) q=(0:12) perror=(3:12); run; 基于对这两个矩阵的分析,SAS接着输出了SCAN方法识别出的模 型,如图17.28所示。待选模型包括ARMA(1,1)、AR(8)和一些非 平稳模型。注意,ARMA(1,1)同样是ESACF方法推荐的待选模型。 图17.27 例17.6中ESACF方法识别出的待选模型 图17.28 例17.6中SCAN方法识别出的待选模型 下面这段程序输出了MINIC方法的信息准则矩阵。 /*---- MINIC ----*/ proc arima data=work.armaExamples; identify var=Y7 nlag=12 minic p=(0:12) q=(0:12) perror=(3:12); run; 该信息准则矩阵如图17.29所示,并且它基于最小信息准则,给出 了推荐的模型ARMA(1,4)。 图17.29 例17.6中MINIC方法的信息准则矩阵 那么,通过这3种方法识别的待选模型有ARMA(1,1)、 ARMA(9,8)、ARMA(10,8)、AR(8)、ARMA(1,4),并 且ESACF方法和SCAN方法都推荐了ARMA(1,1)。 17.2.4 参数估计和诊断检验 1.参数估计 前面已经介绍了任何一个平稳模型都可以用ARMA模型来进行逼 近,不失一般性,我们把ARMA模型表示成如下形式: φ(B)(yt-μ)=θ(B)εt 其中,φ(B)=1-φ1B-φ2B2-…-φp Bp,θ(B)=1-θ1B-θ2B2-…-θqBq。 通过上一节的学习,已经知道如何辨识阶数p和q了,接下来,就要 根据实际数据来拟合模型中的p+q+2个未知参数μ、φ1、φ2、…、φp、 θ1、θ2、…、θp、σ2ε。 参数μ是序列均值,通常采用矩估计方法,用样本均值估计总体均 值即可得到它的估计值: 这样一来,原来p+q+2个待估参数就减少为p+q+1个了。对p+q+1个 未知参数的估计方法有3种:矩估计、极大似然估计和最小二乘估计。 (1)矩估计 运用p+q个样本自相关系数估计总体自相关系数 从中解出的参数值 计。 就是φ1、…、φp、θ1、…、θq的矩估 用序列样本方差估计序列总体方差: 在ARMA(p,q)模型两边同时求方差,整理得到σ2ε和σ2y的关 系,并将 作为φ1、…、φp、θ1、…、θq的估计代入,即得 到白噪声序列方差的矩估计: 在低阶ARMA模型场合下,矩估计方法具有计算量小,估计思想简 单直观,且不需要假设总体分布等特点。但是这种估计方法中只用到了 p+q个样本自相关系数,样本序列中的其他信息都被忽略了,这导致它 的估计精度较差。可见,矩估计方法是一种比较粗糙的估计方法,因此 它的值常被用作极大似然估计和最小二乘估计迭代计算的初始值。 (2)极大似然估计 在极大似然的准则下,认为样本来自使得该样本出现概率最大的总 体。因此,未知参数的极大似然估计就是使得似然函数L(φ1,…, φp,θ1,…,θq)达到最大的参数值,似然函数为: L(φ1,…,φp,θ1,…,θq)=p(y1,y2,…,yn,φ1,…,φp, θ1,…,θq) 这里的p(y1,y2,…,yn,φ1,…,φp,θ1,…,θq)是联合密度函 数。 使用极大似然估计必须已知总体的分布函数,而在时间序列分析 中,序列总体的分布往往是未知的,为了便于计算和分析,通常假设序 列服从多元正态分布。 yt=φ1yt-1+φ2yt-2+…+φpyt-p+εt-θ1εt-1-θ2εt-2-…-θqεt-q 则 的似然函数为: 对对数似然函数中的未知参数 和σ2ε求偏导数,就可以得到似然方 程组。理论上,求解似然方程组即得到未知参数的极大似然估计值。但 是,由于 'Ω-1 和ln|Ω|都不是参数的显式表达式,因而似然方程组实际 上是由p+q+1个超越方程构成的,通常需要经过复杂的迭代算法才能求 出未知参数的极大似然估计值。 极大似然估计法充分应用了每一个观测值所提供的信息,因而它的 估计精度较高,同时还具有估计的一致性、渐进有效性等优良的统计性 质。 (3)最小二乘估计 在ARMA(p,q)模型中,记 残差平方和 使得残差平方和达到最小 的那组参数值即为 的最小二乘估计值。 同极大似然估计一样,由于Q( )不是 的显式函数,未知参数的 最小二乘估计值通常也得借助迭代法求出,并且由于充分利用了序列观 测值的信息,因而最小二乘估计的精度也很高。 在实际运用中,最常用的是条件最小二乘估计法,它指定过去未观 测到的序列值等于样本序列的均值。 虽然极大似然估计的计算量要大于最小二乘估计,但是,研究表 明,极大似然估计要优于最小二乘估计,特别是对样本长度比较小的时 间序列。 ARIMA过程中,考虑到计算量等因素,默认的参数估计方法是条 件最小二乘估计。但是,通过长期的实践,SAS和很多使用SAS进行预 测的专家都推荐使用极大似然估计作为参数估计的方法。 使用ARIMA过程进行参数估计的语法如下: PROC ARIMA DATA=数据集; IDENTIFY VAR=分析变量NLAG=m; ESTIMATE P=Pnum Q=Qnum ML; RUN; 其中,ESTIMATE语句是ARIMA过程中进行参数估计的语句,选 项P指定了进行自回归的阶数,选项Q指定了移动平均的阶数,选项ML 指定了使用极大似然估计作为参数估计的方法,也可以用 METHOD=ML来指定使用极大似然估计法,默认METHOD=CLS,表示 使用条件最小二乘估计法。 例17.7:在例17.6中已经识别出了一组待选模型,下面根据参数使 用简约性原则,对待选模型ARMA(1,1)进行参数估计。 示例代码如下: proc arima data=work.armaExamples; identify var=Y7 nlag=12 noprint; estimate p=1 q=1 ml; run; 输出内容如图17.30和图17.31所示。 在图17.30中,显示的是极大似然估计(也称为最大似然估计)的 参数估计和显著性报表。在ARMA(1,1)模型中,均值项记为MU, 其估计值为-0.01166,φ1对应着“MA1,1”,θ1对应着“AR1,1”,很明 显,参数“MA1,1”和“AR1,1”都是显著不为0的。 图17.30 例17.7中ARMA(1,1)模型参数估计报表 图17.31 例17.7中ARMA(1,1)模型信息 由ARMA模型φ(B) t=θ(B)εt可知,ARMA(1,1)模型可以 写为: (1-0.91309B)(Y7t+0.01166)=(1+0.80654B)εt 由于BY7t=Y7t-1,可得: Y7t+0.01166=0.91309(Y7t-1+0.01166)+εt+0.80654εt-1 当有多组待选模型时,可以在同一ARIMA过程中多次使用 ESTIMATE语句进行参数估计。 2.诊断检验 模型诊断检验过程将通过计算多个诊断统计量来衡量模型的拟合优 度和准确度,并对模型的残差序列进行相关性检验和正态性检验。 衡量模型拟合优度的准则一般有以下两个。 ·AIC准则:即Akaike's Information Criterion,是衡量统计模型拟合 优良性的一种标准,它是由日本统计学家赤池弘次创立和发展的,又称 赤池信息量准则。它建立在熵的概念基础之上,可以权衡所估计模型的 复杂度和该模型拟合数据的优良性。AIC准则鼓励数据拟合的优良性, 但是也表示应尽量避免出现过度拟合(Overfitting)的情况。 ·SBC准则:即Schwarz's Bayesian Information Criterion。和AIC准则 类似,但在计算公式上面略有差别。 当有多个待选模型时,应优先考虑AIC值或者SBC值小的模型。这 两个准则在很多进行模型拟合的过程步中都会用到。 衡量模型准确度的统计量有以下几种。 当序列值有可能为0时,推荐使用SMAPE。MAPE在 商业预测和分析中应用得比较多。当有多个待选模型时,可以通过 计算MAPE和RMSE等统计量来衡量模型的准确性。 在例17.7中,在参数估计报表后面输出了ARMA(1,1)模型的拟 合优度报表,如图17.32所示。 图17.32 例17.7中ARMA(1,1)模型拟合优度报表 接着,又输出了残差序列的诊断检验结果,如图17.33和图17.34所 示。残差序列的ACF图、PACF图、IACF图和白噪声序列Y8的对应相关 系数图非常类似,并且白噪声概率都大于0.05,表示残差序列值之间不 存在依赖关系。残差序列的直方图和Q-Q图也表示残差序列服从正态分 布。 白噪声检验不仅可以用在对原始时间序列的检验中,也可以用在对 残差序列的检验中。如果模型已经从序列中提取出了所有的有用信息, 那么残差序列应该是一个白噪声序列,否则,说明序列中某种规律性的 信息没有被模型表示出来,也就是说,模型是拟合不足的。 图17.33 例17.7中ARMA(1,1)模型残差诊断 图17.34 17.2.5 例17.7中ARMA(1,1)模型残差正态诊断 预测 在参数估计和诊断检验完成后,就可以利用拟合出的模型进行预测 了。ARIMA过程进行预测的语句为FORECAST语句,使用方法如下: PROC ARIMA DATA=数据集选项; IDENTIFY VAR=分析变量NLAG=m; ESTIMATE P=Pnum Q=Qnum ML; FORECAST LEAD=k ID=时间变量INTERVAL=时间间隔OUT=输出数据集<选项>; RUN; 其中,FORECAST语句会根据ESTIMATE语句中使用的模型及估计 出的参数,生成时间序列的预测值。因此,在ARIMA过程中,使用 FORECAST语句前,一定要先使用ESTIMATE语句。选项LEAD指定需 要向后预测多少个时间点,选项ID和INTERVAL分别指定时间变量和时 间间隔,选项OUT指定输出数据集。 接下来,通过一个完整的示例来介绍对一个平稳序列进行分析的全 过程。 例17.8:数据集ex.weeklysales中包含了一家汽车零部件店里3种零 件从2010年1月开始到2012年10月每周的销售量。数据集中包含4个变 量,具体如下。 ·OrderDate:每周的第一天,代表一周。 ·Part1:零部件1每周的销量。 ·Part2:零部件2每周的销量。 ·Part3:零部件3每周的销量。 现在希望预测这3种零件未来每周的销量。 因为数据已经准备完毕,那么,根据时间序列分析的流程,我们可 以跳过第一步,直接来查看序列是否平稳,以及序列中是否包含值得提 取的信息,并进行模型识别。示例代码如下: proc arima data=ex.weeklysales plots=all plots(unpack); identify var=part1 nlag=14 esacf minic scan p=(0:12) q=(0:12) perror=(3:12); run; 选项UNPACK指定系统输出较大的时序图、ACF图、PACF图和 IACF图。 从时序图来看,part1没有明显的趋势或者季节性因素,如图17.35 所示。 图17.35 例17.8中序列PART1的时序图 ACF图中,自相关系数在4阶延迟期后即衰弱成小值振荡,与 MA(4)的特征相符,如图17.36所示。 图17.36 例17.8中序列PART1的自相关图 PACF图和IACF图具有一定的截尾特征,如图17.37所示。在2阶延 迟处显著不为0,和AR(2)的特征相符。结合ACF图的特征,或许也 可以尝试ARMA混合模型。 IDENTIFY语句也同时对part1的序列进行了白噪声检验,输出内容 如图17.38所示。 这说明序列的观测之间存在显著的依赖关系,可以建模。 图17.37 例17.8中序列PART1的偏自相关图和逆自相关图 图17-37 (续) 接着,由于使用选项ESACF、SCAN和MINIC进行了自动模型识 别,系统输出了广义自相关系数矩阵及相应的P值矩阵、典型相关估计 矩阵和卡方统计量的P值矩阵,以及MINIC方法的信息准则矩阵,并基 于对这些矩阵的分析,提供了待选模型组,如图17.39所示。 图17.38 图17.39 例17.8中序列PART1的白噪声检验 例17.8中序列PART1自动识别出的待选模型 MINIC方法推荐的待选模型是AR(2)。假定序列是平稳的, SCAN方法推荐的待选模型为AR(2)和ARMA(1,2),ESACF方法 推荐的待选模型有ARMA(1,2)、ARMA(4,2)等。当d>0时,也 可以得出一些待选的非平稳模型。结合相关系数图,我们可以先将 AR(2)作为part1序列合适的待选模型。 用同样的方法可以分析part2序列和part3序列。代码如下: proc arima date=ex.weeklysales plots=all plots(unpack); identify var=part2 nlags=14 esacf minic scan p=(0:12) q=(0:12) perror=(3:12); identify var=part3 nlags=14 esacf minic scan p=(0:12) q=(0:12) perror=(3:12); run; AR(1)和MA(3)分别是适合part2和part3序列的待选模型。 接下来,进行参数估计和诊断检验,查看用这些模型是否足够拟合 这些序列。代码如下: proc arima data=ex.weeklysales; identify var=part1 noprint; estimate p=2 outest=work.est_part1; run; 序列part1的参数估计和诊断结果如图17.40和图17.41所示。 图17.40 例17.8中序列PART1的AR(2)模型参数估计和拟合优度报表 图17.41 例17.8中序列PART1的AR(2)模型残差诊断 从图中展示的结果可以看到,参数(AR1,1)的p值为0.4870,说 明该参数不是显著非0;并且残差序列在5阶延迟时,p值小于0.05,说 明残差序列不能通过白噪声检验,残差序列中仍然有信息没有被充分提 取。因此,AR(2)模型对于序列part1是不合适的。我们可以尝试使用 AR(3)。代码如下: proc arima data=ex.weeklysales; identify var=part1 noprint; estimate p=3 ml outest=work.est_part1; run; 参数估计和诊断检验的结果如图17.42和图17.43所示。 图17.42 例17.8中序列PART1的AR(3)模型I参数估计和拟合优度报 表 图17.43 例17.8中序列PART1的AR(3)模型I残差诊断 此时,残差序列基本可以通过白噪声检验,但是参数(AR1,1) 仍然是不显著的。因此,可以考虑剔除(AR1,1),重新拟合模型。 代码如下: proc arima data=ex.weeklysales; identify var=part1 noprint; estimate p=(2 3) ml outest=work.est_part1; run; 选项p=(23)表示只使用2阶和3阶自回归项。参数估计如图17.44 所示。 图17.44 例17.8中序列PART1的AR(3)模型II参数估计和拟合优度报 表 这里,参数(AR1,1)表示2阶自回归项的参数,(AR1,2)表 示3阶自回归项的参数,并且这些参数都是显著不为0的。 诊断检验的输出内容如图17.45和图17.46所示。 图17.45 例17.8中序列PART1的AR(3)模型II残差诊断 这说明使用只包含2阶自回归项和3阶自回归项的模型来拟合序列 part1是合适的。 同样的方法,可以用来拟合序列part2和part3,得出适合part2和 part3的模型分别为AR(1)和MA(4)。 接下来,就可以用拟合好的模型进行预测了。代码如下: proc arima data=ex.weeklysales plots(only)=(forecast(forecast)); identify var=part1 noprint; estimate p=(2 3) ml outest=work.est_part1 noprint; forecast lead=8 id=date interval=week out=work.forecast_part1; identify var=part2 noprint; estimate forecast identify estimate forecast run; 图17.46 p=1 ml outest=work.est_part2 noprint; lead=8 id=date interval=week out=work.forecast_part2; var=part3 noprint; q=4 outest=work.est_part3 noprint; lead=8 id=date interval=week out=work.forecast_part3; 例17.8中序列PART1的AR(3)模型II残差正态诊断 选项OUT=指定输出的预测结果分别被保存在数据集 work.est_part1、work.est_part2和work.est_part3中。输出数据集 work.forecast_part1中依次保存了ID变量(Date)、实际序列观测值 (part1)、序列预测值(“part1”预测)、预测标准误差、95%置信下 限、95%置信上限及残差,如图17.47所示。 图17.47 数据集work.est_part1部分内容 系统输出如图17.48至图17.50所示的预测效果图。 图17.48 序列PART1的AR(3)模型II预测效果图 图17.49 序列PART2的AR(1)模型预测效果图 可以看到,part2和part3的预测在两周之后收敛到均值。 平稳时间序列是时间序列中一类重要的序列,可以说,平稳时间序 列分析是时间序列分析的基础。在本节中,针对平稳时间序列进行了介 绍,并结合实例介绍了Box-Jenkins时间序列分析的基本步骤:数据准备 →平稳性和白噪声检验→模型识别→参数估计和诊断检验→预测。需要 指出的是,在实际操作中,很多序列都不是平稳序列,它们有的是带有 趋势的,有的是带有季节性因素的,那么在遇到这种类型的序列时,我 们该如何进行分析呢?这就是接下来两节中需要研究的问题。 图17.50 序列PART3的MA(4)模型预测效果图 17.3 趋势时间序列分析 大量的经验证据表明,经济分析中涉及的大多数时间序列是非平稳 的,特别是宏观经济时间序列,往往呈现出随时间而变化的趋势。非平 稳序列的时间趋势主要有两种,一种是确定性时间趋势,一种是随机性 时间趋势。 17.3.1 确定性时间趋势 所谓确定性时间趋势,从直观意义上讲,是指序列的趋势不是变化 莫测的,可以用趋势线来加以刻画,用统计学的语言来描述,就是指序 列的趋势是时间t的函数。 对确定性趋势的时间序列进行建模的方法为趋势拟合法,就是把时 间作为自变量,相应的序列观察值作为因变量,建立序列值随时间变化 的回归模型的方法。常见的趋势拟合函数有以下这些。 (1)线性函数 如果长期趋势呈现出线性特征,那么可以用线性模型来拟合它,模 型可以具体写为: yt=β0+β1t+εt 式中,εt为随机波动,Tt=β0+β1t就是消除随机波动的影响之后该序 列的长期趋势。 (2)二次函数 如果长期趋势呈现出二次函数的特征,那么可以用二次函数来拟合 它,模型可以表示成: yt=β0+β1t+β2t2+εt (3)对数函数 如果长期趋势呈现出对数函数的特征,模型可以表示成: yt=β0+β1log(t)+εt (4)指数函数 如果长期趋势呈现出对数函数的特征,模型可以表示成: yt=exp(β0+β1t)+εt 在ARIMA过程中,可以将趋势变量作为输入变量引入模型,进行 确定性趋势拟合,基本语法如下: PROC ARIMA DATA=数据集; IDENTIFY VAR=分析变量CROSSCORR=(输入变量1(d1,d2,…,dk) … 输入变量m(d1,d2,…,dk)) <其他选项>; ESTIMATE INPUT=(输入变量) <其他选项>; FORECAST OUT=输出数据集<其他选项>; RUN; 其中,在IDENTIFY语句中,选项CROSSCORR=在括号中指定和分 析变量相关的输入变量,CROSSCORR可以简写为CROSS。如果在输入 变量的后面指定了差分阶数,则系统将分析差分后的输入变量,如果同 时在ESTIMATE语句用选项INPUT=指定了该输入变量,系统将使用差 分后的输入变量进行模型拟合和估计。 例17.9:运用ARIMA过程进行确定性趋势拟合。 首先,用如下代码生成一组趋势时间序列。 data work.testdata; do t=1 to 50; var1=4+3*t+5*rannor(999999); var2=4+3*t+2*t*t+500*rannor(999997); var3=4+3*log(t)+rannor(9999991); output; end; run; 由于需要使用ARIMA过程进行确定性趋势拟合,因此,需要在输 入数据集work.testdata添加趋势变量。代码如下: data work.testdata; set work.testdata end=eof; attrib _LINEAR_ label="Linear Term" _QUAD_ label="Quadratic Term" _LOG_ label="Logarithm Term"; retain _LINEAR_ 0; _LINEAR_+1; _QUAD_=_LINEAR_*_LINEAR_; _LOG_=log(_LINEAR_); output; if eof then do future=1 to 10; t+1; _LINEAR_+1; var1=.; var2=.; var3=.; _QUAD_=_LINEAR_*_LINEAR_; _LOG_=log(_LINEAR_); output; end; run; 这里,新生成了变量_LINEAR_、_QUAD_和_LOG_,这些变量将 进入ARIMA过程中对序列进行拟合。我们也注意到,在输入数据集的 尾部增加了10条新的观测,这10条新观测中,趋势变量都是非缺失值, 序列VAR1、VAR2和VAR3都为缺失,等待ARIMA过程根据趋势变量进 行拟合和预测。进行拟合和预测的代码如下: proc arima data=work.testdata plots(only)=(forecast(forecast)); identify var=var1 cross=(_LINEAR_); estimate input=(_LINEAR_) ml; forecast lead=10; identify var=var2 cross=(_LINEAR_ _QUAD_); estimate input=(_LINEAR_ _QUAD_) ml; forecast lead=10; identify var=var3 cross=(_LOG_); estimate input=(_LOG_) ml; forecast lead=10; run; 通常上面的代码,将得到拟合效果,如图17.51至图17.53所示。 图17.51 例17.9中序列VAR1的预测效果图 图17.52 例17.9中序列VAR2的预测效果图 图17.53 例17.9中序列VAR3的预测效果图 运用趋势拟合法,可以将时间序列中的确定性时间趋势提取出来。 如果时间序列在去除确定性时间趋势后,变成平稳时间序列,那么这种 时间序列也叫做趋势平稳序列。对于趋势平稳序列,我们可以在提取出 确定性趋势之后,运用Box-Jenkins方法进一步提取观测之间的依赖性关 系。 17.3.2 随机时间趋势 另一种时间趋势是随机时间趋势,直观地讲,就是时间序列的趋势 无法用一条单一的趋势线来刻画,在不同的时期,趋势线是变化漂移 的,也就是说,趋势是随机的。对于这一类时间序列,其趋势线不能用 时间的确定性函数来表示了,而是需要通过差分方式来描述。 对于非平稳时间序列,从理论上而言,足够多次的差分运算可以充 分地提取原序列中的非平稳随机趋势。许多非平稳序列在进行差分运算 后会显示出平稳时间序列的性质,因此,对于这类非平稳时间序列可以 使用ARIMA模型进行拟合。 ARIMA模型可以表示为: φ(B)(1-B)dyt=θ(B)εt 记为ARIMA(p,d,q),其中,φ(B)=1-φ1B-φ2B2-…-φpBp, θ(B)=1-θ1B-θ2B2-…-θqBq。若记ωt=(1-B)dyt,则φ(B)ωt=θ(B) εt。由此可见,ARIMA模型的实质就是差分运算和ARMA模型的组合。 特别地,当d=0时,ARIMA(p,d,q)模型实际上就是 ARMA(p,q)模型。 当p=0时,ARIMA(p,d,q)模型实际上就是IMA(d,q)模 型。 当q=0时,ARIMA(p,d,q)模型实际上就是ARI(p,d)模型。 使用ARIMA过程进行随机趋势建模的基本语法如下: PROC ARIMA DATA=数据集<选项>; IDENTIFY VAR=分析变量(d1 d2 … dk)<其他选项>; ESTIMATE <选项>; FORECAST OUT=输出数据集<其他选项>; RUN; 例如: ·“IDENTIFY VAR=VAR1(1);”表示对时间序列VAR1进行一阶 差分,然后分析差分后新的时间序列。 ·“IDENTIFY VAR=VAR1(1,1);”表示对时间序列VAR1进行二 阶差分,然后进行分析,分析的时间序列为(yt-yt-1)-(yt-1-yt-2)=yt2yt-1+yt-2。 ·“IDENTIFY VAR=VAR1(2);”表示对时间序列VAR1进行两步 差分,然后进行分析,分析的实际序列为yt-yt-2。 当ESTIMATE语句中不指定任何选项时,如: estimate; 系统将只拟合均值MU,默认的模型为yt=MU+yt-1+εt,该模型也称 为带漂移项的随机游走模型。 因此,使用ARIMA过程进行非平稳序列建模的第一步就会要识别 差分的阶数d,通过d阶差分将序列转换成平稳序列。一般情况下: ·时间序列蕴含着显著的线性趋势,一阶差分后就可以使得时间序 列转换为平稳序列。 ·时间序列蕴含着曲线趋势,通常低阶(二阶)差分就可以提取出 曲线趋势的影响。 ·对蕴含着固定周期的序列进行步长为周期长度的差分运算,通常 可以较好地提取周期信息。 需要注意的是,差分运算的阶数并不是越多越好。因为差分运算是 一种对信息提取、加工的过程,每次差分都会有信息的损失,所以在实 际应用中,差分运算的阶数要适当,应当避免过度差分。 在ARIMA模型中,当d=1,p=q=0时,ARIMA(0,1,0)模型 为: yt=yt-1+εt 该模型被称为随机游走(Random Walk)模型或者醉汉模型。作为 一个最简单的ARIMA模型,随机游走模型是一个典型的含随机时间趋 势的模型,目前广泛应用于计量经济学领域。传统的经济学家普遍认 为,投机价格的走势类似于随机游走模型,随机游走模型也是有效市场 理论的核心。 另一个重要的ARIMA(0,1,0)模型是带漂移项的随机游走模 型,模型表示为: yt=μ+yt-1+εt 来看图17.54中的两个序列,一个是趋势平稳时间序列,一个是带 漂移项的随机游走序列,eps表示随机干扰。 图17.54 趋势平稳时间序列和带漂移项的随机游走序列 两个序列在图形上非常相似,似乎都包含线性趋势,很难区分。 但是,在实际应用中,如果凭借直观判断,错误地将带漂移项的随 机游走当成趋势平稳过程,用趋势拟合法去除确定性趋势,并进行建模 分析,那么由此而得出的结论是令人怀疑的。 因此,当我们遇到非平稳时间序列时,判断其包含的是确定性趋势 还是随机趋势,对建模方法的选择有很大的影响。对于趋势平稳序列, 可以用趋势拟合法消除确定性趋势;对于包含随机趋势的时间序列,则 需要通过一阶差分将序列转化为平稳序列。 单位根检验 单位根检验(Unit Root Test)方法就是用来判断序列是否需要进行 差分的方法,换言之,也就是用来判断序列是否平稳。在单位根检验法 中,常用的是DF检验法。AR(1)过程可以表示为yt=φ1yt-1+εt,εt是独 立同分布的,并且εt~N(0,σ2ε)。 由本章前面关于自回归模型的讨论可知,上式可以写成 因此φ1必须满足|φ1|<1,才能保证AR(1)过程的平稳性。由于方 程1-φ1B=0的根为φ-11,所以,平稳性条件的另一种等价叙述为:方程1φ1B=0的根必须在单位圆之外。方程1-φ1B=0,也称为AR(1)过程的特 征方程。 所以可以通过检验特征方程的根是在单位圆内(上)还是单位圆 外,来检验序列的平稳性,这种检验就称为单位根检验。 由于现实生活中绝大多数序列都是非平稳序列,因此根据特征方程 根的性质,将单位根检验的原假设和备择假设分别设定为: H0:序列yt非平稳<=>H0:|φ1|=1,即需要进行一阶差分 H1:序列yt非平稳<=>H1:|φ1|<1,即不需要进行一阶差分 当显著性水平为α时,记τα为DF检验的α分位点。 ·当DF统计量≤τα,拒绝原假设,认为序列yt显著平稳,也就是说不 需要进行差分。 ·当DF统计量>τα,接受原假设,认为序列yt非平稳,需要进行差 分。 DF检验可用于以下3种过程的平稳性检验。 ·第一种类型:无常数均值、无趋势的1阶自回归过程,这是我们之 前一直分析的类型。 ·第二种类型:有常数均值、无趋势的1阶自回归过程,yt=μ+φ1yt2 1+εt,εt是独立同分布的,并且εt~N(0,σ ε)。 ·第三种类型:既有常数均值、又有线性趋势的1阶自回归过程, yt=μ+βt+φ1yt-1+εt,εt是独立同分布的,并且εt~N(0,σ2ε)。 DF检验只适用于1阶自回归过程的平稳性检验,但是实际上绝大多 数时间序列不会是一个简单的AR(1)过程。为了使DF检验能广泛地适 用于AR(p)过程的平稳性检验,人们对DF检验进行了一定的修正,得 到了增广DF检验(Augmented Dickey-Fuller),简记为ADF检验。DF检 验是ADF检验的一个特例。 和DF检验一样,ADF检验也可以用于如下3种类型的平稳性检验。 ·第一种类型:无常数均值、无趋势的p阶自回归过程。 ·第二种类型:有常数均值、无趋势的p阶自回归过程。 ·第三种类型:既有常数均值、又有线性趋势的p阶自回归过程。 在ARIMA过程中,使用选项STATIONARY可以指定进行ADF检 验,语法如下: PROC ARIMA DATA=数据集<选项>; IDENTIFY VAR=分析变量 STATIONARITY=(ADF=(AR阶数))<其他选项>; ESTIMATE <选项>; FORECAST OUT=输出数据集<其他选项>; RUN; 其中,当AR阶数为0时,表示进行上面介绍的3种类型(无常数均 值、无趋势;有常数均值、无趋势;有常数均值和线性趋势)的1阶自 回归过程平稳性检验;AR阶数为1时,表示进行上面介绍的3种类型的2 阶自回归过程的平稳性检验;依此类推。可以同时指定进行多个阶数自 回归过程的平稳性检验。 例17.10:数据集ex.production中的序列PROD代表一段时间内某种 工业原料的市场供应量,试分析该序列是否平稳。 proc arima data=ex.production; identify var=prod nlag=12 stationarity=(adf=(0 1)); run; 示例代码如下: 输出如图17.55至图17.56所示(省略白噪声检验结果)。 图17.55 例17.10中序列PROD的时序图 图中报表是序列PROD的时序图和ADF检验结果,类型一列中的“零 均值”、“单均值”和“趋势”分别对应着ADF检验的第一、二、三种类 型。滞后一列有两种取值:0和1,分别对应着AR(1)过程和AR(2) 过程。在SAS中,ARIMA过程的ADF检验,基于不同的方法分别构造了 3种统计量Rho、Tau和F来进行单位根检验,并分别输出了3种统计量的 取值和对应的P值。 图17.56 例17.10中序列PROD的单位根检验报表 一般情况下,3种统计量有如下参考准则: ·F统计量的检验不如Rho统计量和Tau统计量的检验结果准确,不太 推荐参考。 ·当滞后为1(对应着AR(2)过程)时,Rho统计量的检验结果较 Tau统计量更准确;其余情况下,Tau统计量的检验结果更具参考价值。 这里,结合时序图,我们看到序列带有一定的趋势,因此重点参考 类型为“趋势”的单位根检验结果。根据上面介绍的参考准则,对于滞后 为0和1两种情形下,p值都大于0.05,说明不能拒绝原假设,即该序列 是非平稳的。 17.3.3 运用ARIMA过程建立趋势模型 在例17.9中,通过PROD的时序图可以观察到序列具有一定的趋 势,但是如果序列的变异很大,有些时候不一定能够从时序图中看出确 定性的趋势。这时可以借助TIMESERIES过程将序列中的趋势周期、季 节周期等因素进行分解,以便更加明确地判断是否存在趋势或者季节等 因素。 使用TIMESERIES过程进行因素分解的语法如下: PROC TIMESERIES DATA=数据集 OUT=输出数据集 PRINT=(选项) PLOT=(选项) SEASONALITY=n; ID ID变量INTERVAL=时间间隔; VAR 分析变量; DECOMP 选项/ MODE=ADD|MULT|其他; RUN; 其中: ·选项SEASONALITY=可以指定季节周期的长度。例如, SEASONALITY=3表示序列中每3个数据形成一个季节周期。如果没有 使用选项SEASONALITY=指定季节周期的长度,系统将根据ID语句中 的选项INTERVAL来判断季节周期,例如,INTERVAL=MONTH,则 表示SEASONALITY=12。 ·在一个TIMESERIES过程中,只能使用一个DECOMP语句。 DECOMP语句用来进行趋势、季节等因素的分解,常见的选项有 ORIG(原始序列)、TCC(trend-cycle component)、SC(seasonal component)等。选项MODE=ADD时表示所有因素是相加的关系,选项 MODE=MULT时表示所有因素是相乘的关系。 例17.11:数据集ex.demanding中包含了某种工业原料从2003年1月 到2009年9月的市场需求数据。试分析序列demand,并进行建模分析。 第一步:用TIMESERIES过程对序列demand的趋势和季节成分进行 分解,观察序列中是否存在明显的趋势或者季节成分。代码如下: proc timeseries data=ex.demanding out=work.temp print=(descstats) plot=(series decomp tcc sc corr acf pacf iacf wn) seasonality=12; id date interval=month; var demand; decomp tcc sc / mode=mult; run; 图17.57是序列demand中的趋势成分(这里暂不输出季节成分)。 图17.57 例17.11中序列DEMAND的趋势周期成分 该序列的变异比较大,并且存在一些异常值,图中的趋势周期成分 帮助我们观察到序列可能存在二次趋势。 在上面的代码中,还指定输出了序列的相关关系图以及白噪声概率 图,输出如图17.58所示。 在PACF图和IACF图中,偏自相关系数和逆自相关系数在一阶延迟 之后都迅速衰减成小值振荡,和AR(1)过程的特征相符。白噪声检验 揭示该序列不是白噪声序列。 图17.58 例17.11中序列DEMAND的相关性分析 第二步:对序列进行单位根检验,判断序列是否平稳,是否有必要 进行一阶差分,如图17.59所示。 图17.59 例17.11中序列DEMAND的单位根检验报表 单均值和趋势类型下的ADF检验的p值都小于0.05,说明序列是显 著平稳的。因为ADF检验结果显示序列是平稳的,不需要进行一阶差 分,结合序列的自相关系数图,可以考虑使用AR(1)模型。 但是序列的时序图和TIMESERIES过程都揭示了序列具有二次函数 的趋势,这和单位根检验的结果不太一致,因为单位根检验中不能检验 二次趋势。因此在建模分析时,可以考虑多个模型,例如,建立包含以 二次趋势为输入变量的模型,或者单独根据平稳时间序列的判断建立模 型,甚至也可以考虑d≠0的非平稳模型。实际操作中,由于各种各样的 原因,导致这种时序图和检验结果有差别的情况时有发生,当遇到这种 情况时,不能盲目地依赖时序图或者检验结果,在建模型的时候应该充 分考虑多种情况。 用ARIMA过程建立包含确定时间趋势序列的模型时,需要预先生 成趋势变量。_LINEAR_和_SQAURE_是新生成的代表线性和二次关系 的趋势变量。代码如下: data work.demanding; set ex.demanding end=eof; _LINEAR_+1; _SQUARE_=_LINEAR_*_LINEAR_; output; if eof then do t=1 to 24; _LINEAR_+1; _SQUARE_=_LINEAR_*_LINEAR_; demand=.; date=intnx('month',date,1); output; end; drop t; run; 第三步:拟合模型,进行参数估计、诊断检验和预测。 根据ADF检验的结果和序列自相关系数图,先尝试使用平稳序列建 模的方法拟合AR(1)模型。代码如下: proc arima data=work.demanding plots=all ; identify var=demand noprint; estimate p=1 ml; forecast lead=12 id=date interval=month out=work.AR1; run; 为了便于比较,将预测数据输出到数据集work.AR1中,如图17.60 至图17.62所示。 图17.60 例17.11中序列DEMAND的AR(1)模型参数估计和拟合优度 报表 图17.61 图17.62 例17.11中序列DEMAND的AR(1)模型残差诊断 例17.11中序列DEMAND的AR(1)模型残差正态诊断 可以看到,模型中的参数(AR1,1)是显著的,在残差的自相关 检验和正态性检验中,都表示AR(1)模型可以充分拟合序列。 预测效果图如图17.63所示。 图17.63 例17.11中序列DEMAND的AR(1)模型预测效果图 虽然诊断结果显示AR(1)对于demand序列的拟合是足够的,但是 从预测效果图看来,对未来的预测效果和序列的变化趋势明显不符合 的。特别是可以看到,序列的最后6个观测的预测值都明显比实际观测 值大,说明预测的偏差可能较大。 ADF检验虽然认为序列是平稳的,但是预测效果不太理想,因此接 下来要尝试使用非平稳时间序列分析的方法进行建模。代码如下: proc arima data=work.demanding plots=all; identify var=demand(1) nlag=12; run; 输出一阶差分后,序列的自相关系数图如图17.64所示。 图17.64 序列DEMAND(1)的趋势和相关分析 对于差分后的序列,自相关系数图显示AR(1)或者MA(1)是合 适。 根据原始序列demand,对ARIMA(1,1,0)模型进行拟合。代码 如下: proc arima data=work.demanding plots=all; identify var=demand(1) nlag=12 noprint; estimate p=1 ml; forecast lead=12 id=date interval=month out=work.D1_AR1; run; 图17.65和图17.66中显示的是ARIMA(1,1,0)模型拟合优度报 表和诊断检验的结果,表明ARIMA(1,1,0)模型合格。 图17.65 序列DEMAND的ARIMA(1,1,0)模型拟合优度报表 图17.66 序列DEMAND的ARIMA(1,1,0)模型残差诊断 ARIMA(1,1,0)的预测效果如图17.67所示。 图17.67 序列DEMAND的ARIMA(1,1,0)模型预测效果图 ARIMA(1,1,0)的预测效果比较合理,没有出现AR(1)那样 明显的偏差。但是该模型95%的预测置信域很宽,置信域越宽表明预测 的精度越低。 TIMESERIES过程明显显示序列具有二次趋势,因此,可以继续尝 试引入确定性趋势。代码如下: proc arima data=work.demanding plots=all; identify var=demand crosscorr=(_linear_ _square_) noprint; estimate input=(_linear_ _square_) ml; forecast lead=0 out=work.resi; run; quit; 参数估计和诊断检验结果如图17.68和图17.69所示。 图17.68 序列DEMAND引入外部变量的模型参数估计和拟合优度报表 图17.69 序列DEMAND引入外部变量的模型残差诊断 参数估计的结果显示线性趋势变量和二次趋势变量的系数都是显著 不为0的,但是残差诊断显示,仅根据线性趋势变量和二次趋势变量拟 合出的回归模型没有能将序列中的自相关关系考虑进去。因此,仍然需 要对残差序列进行建模。此时,可以运用ADF检验对残差序列的平稳性 进行检验。这里将ADF检验这一步省略。 上面demand的残差相关诊断中,PACF图和IACF图都显示AR(1) 过程可以用来拟合残差。因此,我们有以下代码: proc arima data=work.demanding plots=all; identify var=demand crosscorr=(_linear_ _square_) noprint; estimate input=(_linear_ _square_) p=1 ml; forecast lead=12 id=date interval=month out=work.Quad; run; quit; 参数估计和诊断检验的结果如图17.70和图17.71所示。 图17.70 序列DEMAND引入外部变量的AR(1)模型参数估计和拟合 优度报表 图17.71 序列DEMAND引入外部变量的AR(1)模型残差诊断 残差诊断检验显示该模型是合适的,但是,参数估计报表显示线性 趋势变量的系数不显著。该模型的预测效果图如图17.72所示。 图17.72 序列DEMAND引入外部变量的AR(1)模型预测效果图 95%的置信域其宽度明显收窄,但是预测效果中仍然没有能将序列 末端的迅速衰减描述出来,因此,我们猜想,可能序列末端的迅速衰减 是和一些外部数据(如,经济指标和商业指标等)相关。如果需要进一 步改善预测,需要在预测的过程中加入外部变量。考虑外部变量的时间 序列分析不属于本章的范畴,读者如果有兴趣可以参考SAS帮助文档。 第四步:模型比较。 针对这个时间序列建立3个模型,如表17.2所示。其中AIC和SBC值 代表各个模型的拟合优度信息。 表17.2 例17.11中模型汇总 从拟合优度来看,ARIMA(1,1,0)模型要优于其他两个模型。 注意 在选择模型时,要综合考虑其他因素,比如预测效果、模 型对数据的解释效果等。不能盲目依赖拟合优度选择模型。 通过例17.11,基本把时间序列分析的整个过程都实践了一遍。接 下来,将介绍如何进一步改进模型,提高预测的准确度。 17.3.4 异常点检测 本节将介绍如何运用ARIMA过程进行序列的异常点检测,并运用 这些异常点来改善预测效果。异常点指的是不能从历史观测中推断出来 的那些突变的数据点。ARIMA过程中进行异常点检测的基本语法如 下: PROC ARIMA DATA=数据集; IDENTIFY VAR=分析变量<其他选项>; ESTIMATE <选项>; OUTLIER TYPE=(AO LS TC(d1 d2)) MAXNUM=n ID=ID变量<其他选项>; FORECAST OUT=输出数据集<其他选项>; RUN; 其中,使用OUTLIER语句之前必须先使用ESTIMATE语句,选项 MAXNUM指定最多检测多少个异常点。OUTLIER语句中可以检测3种 类型的异常点,如图17.73所示。 ·选项ADDITIVE用来指定检测脉冲型异常点,ADDITIVE可以简写 为AO。 ·选项SHIFT用来指定检测永久位移异常点,SHIFT可以简写成LS。 ·选项TEMP(d1d2…)用来检测持续时长为d1或者d2的位移异常 点,可以简写成TC(d1d2…)。 图17.73 3种类型的异常点图示 例17.12:在例17.11中,使用了各种方法进行建模,但是预测效果 始终不是太好。这里考虑在ARIMA(1,1,0)模型的基础上,使用 OUTLIER语句对demand序列进行异常点进行检测,然后在此基础上对 ARIMA(1,1,0)模型进行改进。 示例代码如下: proc arima data=work.demanding; identify var=demand(1) noprint; estimate p=1 ml; outlier type=(ao ls tc(5)) maxnum=5 id=date; quit; 选项MAXNUM=9指定检测9个异常点,但序列中可能不止存在9个 异常点,默认情况下,选项MAXNUM的取值为5。异常点的输出如图 17.74所示。 图17.74 例17.12中序列DEMAND的OUTLIER详细信息 找到这些异常点之后,即可根据这些异常点的位置极其类型创建新 的输入变量,并将这些变量作为输入变量引入模型中。代码如下: data work.demanding; set work.demanding; AO_OCT2005=('01OCT2005'd<=date<='31OCT2005'd); AO_JUL2003=('01JUL2003'd<=date<='31JUL2003'd); AO_MAR2003=('01MAR2003'd<=date<='31MAR2003'd); AO_AUG2005=('01AUG2005'd<=date<='31AUG2005'd); AO_MAR2009=('01MAR2009'd<=date<='31MAR2009'd); AO_JUN2008=('01JUN2008'd<=date<='30JUN2008'd); LS_JUN2003=(date>='01JUN2003'd); TC5_JAN2004=('01JAN2004'd<=date<='31MAY2004'd); TC5_FEB2003=('01FEB2003'd<=date<='30JUN2003'd); run; proc arima data=work.demanding plots=all; identify var=demand crosscorr=(_linear_ _square_ AO_OCT2005 AO_JUL2003 AO_MAR2009 AO_JUN2008 TC5_JAN2004 TC5_FEB2003 AO_MAR2003 AO_AUG2005 LS_JUN2003) noprint; estimate p=(1 3) input=(_linear_ _square_ AO_OCT2005 AO_JUL2003 AO_MAR2009 AO_ JUN2008 TC5_JAN2004 TC5_FEB2003 AO_MAR2003 AO_AUG2005 LS_JUN2003) ml; forecast lead=12 id=date interval=month ; run; 模型的参数估计和残差诊断检验结果都显示模型是合适的。相较于 前面3个模型,该模型的预测效果也更加合理,如图17.75所示。 17.3.5 运用其他过程建立趋势模型 前面已经介绍了使用ARIMA过程对平稳序列和非平稳序列进行建 模的方法。除了ARIMA过程,SAS/ETS中的FORECAST过程、 AUTOREG过程和ESM过程也可以对时间序列进行分析和预测。 图17.75 例17.12中序列DEMAND的预测效果图 1.FORECAST过程 FORECAST过程可以为成百上千个时间序列快速自动地生成预测。 和ARIMA过程不同的是,FORECAST过程仅考虑序列和时间以及自回 归项的关系,而不能考虑序列和移动平均项的关系,也不能在建模过程 中引入其他输入变量。 运用FORECAST过程进行时间序列预测的基本语法如下: PROC FORECAST DATA=数据集OUT=输出数据集 OUTEST=参数集 TREND=1|2|3 AR=n METHOD=STEPAR|EXPO ... SLENTRY=值1 SLSTAY=值2 INTERVAL=时间间隔 LEAD=m <其他选项>; BY 分类变量; ID ID变量; VAR 分析变量; RUN; 其中: ·当要为多个时间序列进行预测时,BY语句中的分类变量的不同水 平即代表不同的时间序列的名称。 ·选项TREND=1表示使用无趋势模型,TREND=2表示使用线性趋势 模型,TREND=3表示使用二次趋势模型。 ·选项AR=n表示在模型中最多考虑n阶自回归项。 ·选项METHOD=STEPAR表示使用逐步回归的方法进行自回归项的 选择,这方法的原理和线性回归中的STEPWISE方法类似,只是这里选 择的自变量都是自回归项。当选项METHOD=EXPO时表示使用指数平 滑法。 ·选项SLENTRY=和SLSTAY=等号后面的取值规则也和线性回归中 类似,SLENTRY=和SLSTAY=等号后面的取值都必须在0和1之间,用 于控制自回归项是否进入模型,或者是否被剔除出模型,仅在 MEHTOD=METHOD=STEPAR时起作用。默认情况下, SLENTRY=0.2,当自回归项的参数的p值小于0.2时,该自回归项被选入 模型;默认SLSTAY=0.05,当模型中自回归项的参数的p值大于0.05 时,则该自回归项将被剔除出模型。 例17.13:使用FORECAST过程对序列demand进行预测。 示例代码如下: proc forecast data=ex.demanding out=work.demandARfor outall outest=work.paramerters method=stepar ar=6 trend=3 interval=month lead=12; var demand; id date; run; FORECAST过程不输出任何报表。观测值、预测值和残差都保存在 work.demandARfor中,参数估计和模型的诊断都保存在work.parameters 中。这两个数据集的示例如图17.76和图17.77所示。 图17.76 图17.77 2.AUTOREG过程 数据集work.demandARfor部分内容 数据集work.parameters部分内容 在使用ARIMA模型拟合时间序列时,对残差序列有个重要的假 定,即残差序列是均值为0、方差相等的白噪声序列。换言之,成为残 差序列要满足如下3个条件: ·均值为零。 ·具有纯随机性,即序列之间不存在任何相关性。 ·具有方差齐性特征,即残差序列的方差是常数。 如果方差齐性的假定不成立,那么残差序列的方差就不再是常数, 它会随着时间的变化而变化,可以表示为时间的某个函数,这种情况被 称作异方差。 在残差序列的这3个假定中,实现零均值的假定只需对序列进行中 心化处理就可以,无需检验;纯随机性的假定可以通过构造统计量进行 检验,也就是前面介绍的白噪声检验。只有第3个假定——方差齐性的 假定,我们没有进行任何检验方法。对于平稳序列,方差齐性的条件是 自动满足的;对于非平稳序列,在默认检验的情况下,是默认残差序列 一定满足这个条件的。但是,实际上,这个假定条件并不总是满足。忽 视异方差的存在会导致残差的方差被严重低估,继而致使参数显著性检 验失去意义,导致模型的拟合精度受影响。在序列异方差和自相关的情 况下,可以使用自回归误差模型进行预测。自回归误差模型的形式如 下: yt=X'tβ+zt zt=φ1zt-1-φ2zt-2-…-φmzt-m+εt εt~N(0,σ2) 其中: Xt=(x1,…,xk)为自变量组成的向量,x1,…,xk也称为输入变 量;β=(β1,…,βk)是对应自变量的系数组成的向量;εt服从均值为 0,方差为σ2的正态分布。 SAS提供了AUTOREG过程拟合自回归误差模型。AUTOREG过程 还可以进行包括ADF检验在内的平稳性检验,并且集成了多种参数估计 方法,如Yule-Walker估计、条件最小二乘估计法和极大似然估计法。 使用AUTOREG过程进行预测的基本语法如下: PROC AUTOREG DATA=数据集<其他选项>; MODEL 分析变量=输入变量1 输入变量2 ... < /NLAG=n BACKSTEP <其他选项>>; RUN; 其中: ·分析变量表示需要预测的序列,相当于因变量;输入变量表示除 了分析变量之外的其他对分析变量有影响的变量,相当于自变量。例 如,例17.11中构造的变量_LINEAR_和_SQUARE_。 ·选项NLAG=n表示最多考虑n阶自回归误差项,选项BACKSTEP表 示在对自回归误差项进行选择时,使用向后消除法,和线性回归中的 BACKSTEP方法一样。 例17.14:运用AUTOREG过程,拟合以下时间序列。 示例代码如下: data work.exampleautoreg; input x @@; t=_N_; datalines; 3.03 8.46 10.22 9.80 11.96 2.83 8.43 13.77 16.18 16.84 19.57 13.26 14.78 24.48 28.16 28.27 32.62 18.44 25.25 38.36 43.70 44.46 50.66 33.01 39.97 60.17 68.12 68.84 78.15 49.84 62.23 91.49 103.20 104.53 118.18 77.88 94.75 138.36 155.68 157.46 177.69 117.15 ; run; proc sgplot data=work.exampleautoreg; scatter x=t y=x; series x=t y=x; run; 以上程序生成一个时间序列X,并作出了该时间序列的时序图,如 图17.78所示。 图17.78 例17.14中序列X的时序图 该时间序列显然是非平稳的,对其进行一阶差分后,时序图如图 17.79所示。 图17.79 例17.14中序列X进行一阶差分后的时序图 一阶差分后,序列的方差随着时间的变化越来越大,具有明显的异 方差性质。 调用AUTOREG过程进行拟合。代码如下: proc autoreg data=work.exampleautoreg; model x = t /nlag=12 backstep; output out=work.Autoreg_output UCL=U95 LCL=L95 Predicted=Forecast; run; proc sgplot data=work.Autoreg_output; band x=t lower=L95 upper=U95; scatter x=t y=x; series x=t y=forecast; run; AUTOREG过程中将数据集work.exampleautoreg中的变量、计算出 的预测值、95%置信上限和95%置信下限都保存到数据集 work.Autoreg_demand,同时,将上下置信限和预测值的列名分别更改 为了U95、L95和Forecast。 预测效果如图17.80所示。 图17.80 例17.14中序列X的预测效果图 其中,深色区域是预测值95%的置信区域,圆点是实际值,折线代 表着预测值。从预测效果来看,AUTOREG过程很好地拟合了该时间序 列。这里省略了AUTOREG过程中的参数估计和拟合诊断检验的输出内 容。 需要注意的是,在AUTOREG过程中不能使用选项LEAD指定预测 的期数,这是因为AUTOREG过程中包含输入变量,而过程自身是不能 预测输入变量的。这一点和REG过程类似。因此,AUTOREG过程更加 适合用于插值和推测。这里如果要用AUTOREG过程实现对未来的预 测,需要对输入数据集work.Exampleautoreg进行扩充,并对输入变量t赋 值。 此外,AUTOREG过程还可以拟合ARCH模型、GARCH模型和 IGARCH模型等,有兴趣的读者可以查看SAS帮助文档进一步了解。 3.ESM过程 平滑法是进行趋势分析和预测时常用的一种方法。它是利用修匀技 术,消弱短期随机波动对序列的影响,使序列平滑化,从而显示出变化 的规律。它具有调节灵活、计算简便的特征,广泛应用于计量经济、人 口研究等诸多领域。根据所用的技术不同,平滑法可以具体分为移动平 均法和指数平滑法。 移动平均法的基本思想是对于一个时间序列,可以假定在一个比较 短的时间间隔里,序列的取值是比较稳定的,它们之间的差异主要是由 随机波动造成的。根据这种假定,可以用一定时间间隔内的平均值作为 某一期的估计值。以n期移动平均为例, 相当于用近n期的加 权平均数作为最后一期趋势的估计值,它们的权重都取为 实际上也就 是假定无论时间的远近,这n期的观测值yt,yt-1,…,yt-n+1对最后一期 的影响都是一样的。 但在实际生活中,我们会发现对大多数随机时间而言,一般都是近 期的结果对现在的影响会大些,远期的结果对现在的影响会小些。为了 更好地反映这种影响的作用,将考虑时间间隔对事件发展的影响,即各 期权重随时间间隔的增大而对现在的影响呈指数衰减。这就是指数平滑 法的基本思想。 SAS的ESM过程支持以下三大类(七小类)指数平滑模型: (1)带线性趋势的指数平滑模型 1)简单指数平滑模型 2)二次指数平滑模型(也称为Brown指数平滑模型) 3)Holt线性指数平滑模型 4)阻尼趋势指数平滑模型 (2)带季节性的指数平滑模型 季节指数平滑模型 (3)带线性趋势和季节性的指数平滑模型 1)Winter加法指数平滑模型 2)Winter乘法指数平滑模型 以简单指数平滑模型为例,假设有时间序列y1,y2,…,yt,…, yn,简单指数平滑值的计算公式如下: St=ωyt+(1-ω)St-1 其中,St为第t期简单指数平滑值,yt为第t期的观测值,ω为水平权 重,0<ω<1。 因此,我们有: St=ωyt+(1-ω)St-1 St-1=ωyt-1+(1-ω)St-2 … S1=ωy1+(1-ω)S0 通过迭代可得, St=ωyt+ω(1-ω)yt-1+ω(1-ω)2yt-2+…+(1-ω)t S0 预测公式为: ,即将当期的简单指数平滑值作为下一期的预 测值,也可以写成以下形式: 可以看出,下一期的预测值实际上是在原预测值的基础上利用误差 进行调整。 初始值S0的取法有很多种,通常会以前n个观测的平均值作为初始 值。从预测公式来看,在给定初始值S0后,简单指数平滑模型只依赖于 过去的观测值和水平权重ω。我们在分析序列的时候,就是要根据实际 观测值,估计出ω的合理取值。 在其他带趋势的指数平滑模型中,除了需要估计水平权重ω以外, 有的还需估计趋势权重γ和阻尼权重φ;而在带季节性的指数平滑模型 中,还需要估计季节权重δ。 调用ESM过程进行指数平滑建模的基本语法如下: PROC ESM DATA=数据集OUT=输出数据集1 OUTEST=输出数据集2 OUTFOR=输出数据集3 OUTSTAT=输出数据集4 OUTSUM=输出数据集5 SEASONALITY=n PLOT=选项|(选项) PRINT=选项|(选项) LEAD=m <其他选项>; BY 分类变量; ID ID变量INTERVAL=时间间隔 FORECAST 分析变量1 <分析变量2 ...> / MODEL=关键字<其他选项>; RUN; 其中: ·选项OUT=输出包含FORECAST语句中指定的分析变量预测值的数 据集。 ·选项OUTET=输出包含参数估计、假设检验的统计量、p值等数据 的数据集。 ·选项OUTFOR=输出包含实际值、预测值、置信上限、置信下限、 残差、预测标准差的数据集。 ·选项OUTSTAT=输出包含模型拟合优度信息的数据集,便于用来 进行不同模型的比较。 ·选项OUTSUM=输出汇总数据。 ·FORECAST语句中选项MODEL有7种取值,分别对应7种不同类型 的指数平滑模型,如表17.3所示。 表17.3 指数平滑模型汇总 例17.15:运用ESM过程,对demand序列进行预测。 示例代码如下: proc esm data=ex.demanding outfor=work.outfor(rename=(LOWER=L95 UPPER=U95 PREDICT=Forecast)) print=(estimates statistics summary) seasonality=12 lead=12; id date interval=month; forecast demand / model=linear; run; proc sgplot data=work.outfor; band x=date upper=U95 lower=L95; scatter x=date y=actual; series x=date y=forecast; refline '01Oct2009'd /axis=x; run; 在ESM过程中,用线性指数平滑模型拟合了序列demand,并输出 了模型的参数估计和各种拟合统计量,如图17.81所示。参数估计结果 显示模型的趋势权重参数不显著。 图17.81 例17.15中序列DEMAND的线性指数平滑模型参数估计和拟合 统计量报表 由于ESM过程的预测数据输出在work.outfor中,因此运用SPLOT过 程输出的预测效果图如图17.82所示。 图17.82 例17.15中序列DEMAND的线性指数平滑模型预测效果图 17.4 季节时间序列模型 在日常生活中,可以见到许多有季节因素的时间序列,比如:四季 的气温、每个月的商品零售额、某自然景点每季度的旅游人数等,它们 都会呈现出明显的季节变动规律。我们还可以把“季节”广义化,凡是呈 现出固定周期性变化的事件,我们都称它具有“季节”因素。 时间序列中的季节因素通常可以分为两种:一种是确定性季节因 素,一种是随机季节因素。 17.4.1 确定性季节因素 包含确定性季节因素的时间序列,从直观上讲,每隔一个周期的观 测都具有明显的相似性,对于这种类型的序列,可以通过创建季节性虚 拟变量,并在建模时将季节性虚拟变量作为输入变量引入回归模型中, 然后进行有效的建模分析。 对于周期为S的季节性时间序列,可以创建S个季节性虚拟变量,每 一期一个虚拟变量。例如,对于月度数据,周期为12,可以创建虚拟变 量IJan,IFeb,…,IDec,其中一月的虚拟变量IJan定义如下: 对时间序列建立模型y=βJanIJan+βFebIFeb+…+βDecIDec,βM代表第M个 月对时间序列的影响,通过拟合模型,可以计算出βM的估计量。 如果模型中含有常数项,y=cons+βJanIJan+βFebIFeb+…+βNovINov,则 只需创建S-1个季节性虚拟变量,cons+βM代表第M个月对时间序列的影 响,cons代表最后一个月对时间序列的影响。 例17.16:数据集ex.airline1990_2013中包含了从1990年1月到2013年 12月美国国内航线旅客数量。PASSENGERS序列的时序图如图17.83所 示。 图17.83 例17.16中序列PASSENGERS的时序图 首先重点分析数据的前半段,序列PASSENGERS含有明显的确定 性季节因素和时间趋势。因此,可以创建季节性虚拟变量和趋势变量。 代码如下: data work.Air1990_2000; set ex.airline1990_2013(where=(month<='31Dec2000'd)); array seasons{*} mon1-mon11; retain mon1-mon11 . time 0; time+1; if mon1=.then do i=1 to 11; seasons[i]=0; end; if month(month)<12 then do; seasons[month(month)]=1; output; seasons[month(month)]=0; end; else output; drop i; run; 季节性虚拟变量示例如图17.84所示。 图17.84 数据集work.Air1990_2000部分内容 和分析确定性趋势的时间序列一样,我们调用ARIMA过程进行建 模,并把虚拟变量mon1-mon11和时间趋势变量time作为输入变量引入模 型中。代码如下: proc arima data=work.Air1990_2000; identify var=passengers crosscorr=( time mon1 mon2 mon3 mon4 mon5 mon6 mon7 mon8 mon9 mon10 mon11 ) noprint; estimate input=(time mon1 mon2 mon3 mon4 mon5 mon6 mon7 mon8 mon9 mon10 mon11 ) ml; quit; 得出的参数估计和诊断如图17.85和图17.86所示。 图17.85 例17.16中序列PASSENGERS引入外部变量的模型参数估计报 表 季节虚拟变量和趋势变量基本都是显著的,但是模型没有通过白噪 声检验,说明序列中的自相关关系没有被提取出来。因此,需要在模型 中引入合适的自回归项或移动平均项,例如,考虑对残差序列建立 AR(1)模型。代码如下: proc arima data=work.Air1990_2000; identify var=passengers crosscorr=( time mon1 mon2 mon3 mon4 mon5 mon6 mon7 mon8 mon9 mon10 mon11 ) noprint; estimate input=(time mon1 mon2 mon3 mon4 mon5 mon6 mon7 mon8 mon9 mon10 mon11 ) p=1 ml; quit; 另一种考虑确定性季节性因素的方法是通过创建三角函数变量,并 将这些三角函数变量作为输入变量引入模型中。 图17.86 例17.16中序列PASSENGERS引入外部变量的模型残差诊断 在这个例子中,可以创建周期为4的三角函数变量S4和C4,以及周 期为12的三角函数变量S12和C12。代码如下: data work.air1990_2000; set work.Air1990_2000; retain twopi . time 0; if twopi=.then twopi=2*constant("pi"); time+1; s4=sin(twopi*time/4); c4=cos(twopi*time/4); s12=sin(twopi*time/12); c12=cos(twopi*time/12); format s4 c4 s12 c12 comma6.4; drop twopi ; run; 新生成的数据集示例如图17.87所示。 图17.87 加入三角函数变量的数据集work.air1990_2000部分内容 然后和前面运用季节性虚拟变量一样,将S4、C4、S12和C12作为 输入变量引入时间序列中。这里就不赘述。 17.4.2 随机季节模型 随机季节模型是对季节性时间序列中不同周期上同一位置点之间的 随机季节因素拟合的模型,是Box-Jenkins方法中的季节模型。 以序列PASSENGERS为例,季节效应意味着某一特定月份的观测 值之间是相关的,例如,某一个4月的观测值与以往4月的观测值是相关 的。假设第t个观测值yt就是4月的值,那么可以通过如下形式的模型将 该观测值yt与以往4月的观测值联系起来: 其中S=12, 差项。类似地,模型 分别是BS的P次和Q次多项式,αt为误 可以用来把当前3月的特征和以往3月 的观测值相联系,依此类推,对于12个月中的每个月都可以这样做。并 且,假设在这些月度模型中所含参数Φ和Θ对每个月都近似相等,这种 假设是合理的。 现在这些模型中的误差分量αt,αt-1,…一般是相关的。例如,1995 年4月的旅客总数和以往4月的旅客总数相关,与此同时它也会和1995年 3月、2月、1月等月的总数相关。由此,我们可以料想αt与αt-1相关,还 与αt-2相关,等等。因此,为了在建模中考虑这种联系,我们引入第二 个模型: 其中,εt是随机干扰,φ(B)和θ(B)分别是B的p次和q次多项 式,最终,得到模型 该模型记为ARIMA(p,d,q)(P,D,Q)S,也称为BoxJenkins季节模型。其中: ·p、d、q是非季节因素模型的阶数。 ·P称为季节自回归项的阶数,Q称为季节移动平均项的阶数,D是 季节差分的阶数,S是周期长度,也就是季节差分的步长。 在建立季节ARIMA(p,d,q)(P,D,Q)S模型时,需要首先 识别上面提到的这6个参数p、d、q和P、D、Q,然后再进行参数估计和 诊断检验等。 ARIMA(0,0,0)(1,1,1)12模型的一个特例表示为,只包含 12阶季节自回归项和12阶移动平均项: (1-Φ1B12)(1-B12)yt=θ0+(1-Θ1B12)εt 用ARIMA过程拟合该模型的方法如下: proc arima data=inputdata; identify var=y(12) noprint; estimate p=(12) q=(12) ml; run; ARIMA(1,1,1)(1,1,1)12模型的一个特例为: (1-Φ1B1)(1-Φ1B12)(1-B12)(1-B1)yt=θ0+(1-θ1B1)(1Θ1B12)εt 或者,等价的表示为 zt-Φ1zt-1-Φ1zt-12+Φ1Φ1zt-13=θ0+εt-θ1εt-1-Θ1εt-12+θ1Θ1εt-13 且zt=yt-yt-1-yt-12+yt-13 用ARIMA过程拟合该模型的方法如下: proc arima data=inputdata; identify var=y(1 12) noprint; estimate p=(1)(12) q=(1)(12) ml; run; 用ARIMA过程拟合只包含一阶移动平均项和12阶季节移动平均项 的ARIMA(0,1,1)(0,1,1)12模型的方法如下: proc arima data=inputdata; identify var=y(1 12) noprint; estimate q=(1)(12) ml; run; 一般的ARIMA(p,d,q)(P,D,Q)S,展开来可以写成: (1-Φ1B1-Φ2B2-…-ΦpBp)(1-Φ1B1-Φ2B2-…-ΦPBP)(1-BS)D(1B)d(yt-μ) =(1-θ1B1-θ2B2-…-θqBq)(1-Θ1B1-Θ2B2-…-ΘQBQ)εt 更一般的Box-Jenkins季节模型,可以将确定性时间趋势和确定性季 节因素都考虑进去,表示为: (1-Φ1B1-Φ2B2-…-ΦpBp)(1-Φ1B1-Φ2B2-…-ΦPBP)(yt-f(t)g(t)) =(1-θ1B1-θ2B2-…-θqBq)(1-Θ1B1-Θ2B2-…-ΘQBQ)εt 其中,f(t)表示确定性季节趋势函数,g(t)表示确定性季节因 素函数。 17.4.3 季节性诊断 然而,在我们开始研究一个时间序列的时候,通常并不知道它是否 存在季节性,也不知道它的周期是多少。因此,拿到一个时间序列,除 了要看它是否是平稳序列之外,也要分析它是否存在季节因素,以及周 期为多长。诊断季节性因素的方法主要有两种,一种是通过观察序列的 自相关系数图,一种是运用SPECTRA过程对序列进行傅里叶分解,并 从中得出序列的周期信息。 对于PASSENGERS序列,我们来观察它的自相关系数图,如图 17.88所示。 图17.88 PASSENGERS序列的自相关系数图 在图17.88中,我们看到在延迟为12的地方,自相关系数出现峰 值,也就是说,每隔11个时段,观测之间具有显著的相关关系,这就是 暗示了序列中可能存在周期为12的季节性因素。 在进行了向后12阶差分后,延迟为12的自相关系数峰值消失,落在 了2倍标准误差范围以内,说明进行差分可以消除季节因素,如图17.89 所示。 图17.89 PASSENGERS序列进行12阶差分后的自相关图 运用SPECTRA过程检查数据是否具有周期性,基本语法如下: PROC SPECTRA DATA=数据集OUT=输出数据集 COEF P S <其他选项>; VAR 分析变量; WEIGHTS PARZEN | BART| TUKEY| TRUNCAT| QS <n m>; RUN; 其中,SPECTRA过程不输出任何报表,选项P指定系统将周期图参 数输出到输出数据集中,周期图参数存储为变量P_xx,例如P_01表示第 一个分析变量的周期图参数;选项S指定系统将谱密度参数输出到输出 数据集中,谱密度参数存储为变量S_xx,例如S_01表示第一个分析变量 的谱密度参数;COEF指定系统将傅里叶系数输出到输出数据集中, WEIGHT语句决定了对周期图进行平滑的方法。 SPECTRA过程的原理涉及时间序列分析的另一种方法,这里不展 开讲,只结合例子介绍如何根据SPECTRA过程输出的结果对序列的周 期性进行判定。 例17.17:运用SPECTRA过程检查数据集work.air1990_2000中 PASSENGERS序列的周期性。 示例代码如下: proc spectra data=work.air1990_2000 out=work.periodogram p s; var passengers; weights parzen; run; proc sgplot data=work.periodogram(where=(2<period<=20)); series x=period y=S_01/ lineattrs=graphprediction(pattern=1 color=black) legendlabel="Parzen Kernel" name="series1"; refline 2.4/axis=x lineattrs=graphprediction(pattern=2 color=lightblue thickness=1) legendlabel="Period 2.4" name="series2"; refline 4/axis=x lineattrs=graphprediction(pattern=2 color=blue thickness=3) legendlabel="Period 4" name="series3"; refline 12/axis=x lineattrs=graphprediction(pattern=2 color=darkblue thickness=5) legendlabel="Period 12" name="series4"; keylegend "series1" "series2" "series3" "series4"/ location=outside position=bottom; run; 由于SPECTRA过程只输出数据集,为了更直观地观察到 PASSENGERS序列的周期,我们运用SGPLOT过程作出PASSENGERS 序列的谱密度图(这里的3个REFLINE语句都是根据观察谱密度图后作 出),输出结果如图17.90所示。 图17.90 序列PASSENGERS的谱密度图 在Period=12、Period=4和Period=2.4处,谱密度参数存在极值,并 且在Period=12处,极值比其余极值大。因此,可以认为PASSENGERS 序列中的主要周期为12,和自相关函数判断出的结果一致。 至此,已经判断出序列PASSENGERS存在周期为12的季节性因 素,但是要判断这个季节性因素是适合用确定性季节因素来处理,还是 运用随机季节模型(即进行季节差分)来处理,没有明确的判断标准, 在进行建模的时候可以都进行尝试,可建立多个备选模型。 在前面的介绍中,我们知道用ADF检验法可以进行平稳性检验,事 实上,它也可以用来检验是否需要季节性差分,称为季节性ADF检验。 和平稳性检验类似,季节性ADF检验的原假设为需要进行季节性差分, 备择假设为不需要进行季节差分。 例17.18:检查序列PASSENGERS是否需要进行季节差分。 示例代码如下: proc arima data=work.air1990_2000; identify var=passengers stationarity=(adf=(0 1 2)); identify var=passengers stationarity=(adf=(0 1 2) dlag=12); run; 在上述程序中,第一个IDENTIFY语句用来进行平稳性检验;第二 个IDENTIFY语句,在选项STATIONARITY中增加了选项DLAG=12, 表示检验是否需要进行步长为12的季节性差分,来看检验结果。 ADF单位根检验表示序列是趋势平稳的,如图17.91所示。 季节性ADF检验显示不能拒绝需要进行季节性差分的原假设,如图 17.92所示。 图17.91 图17.92 例17.18中序列PASSENGERS的单位根检验报表 例17.18中序列PASSENGERS的季节性单位根检验报表 例17.19:在例17.16~例17.18中,我们分别通过自相关函数图、 SPECTRA过程及ADF平稳性检验、季节性ADF检验,得知 PASSENGERS序列具有趋势性,且含有周期为12的季节性因素,同时 还创建了三角函数变量S4、C4、S12、C12,季节虚拟变量MON1- MON11,以及趋势变量TIME,存储在数据集work.air1990_2000,基于 以上分析现在对PASSENGERS序列建立模型。 Box和Jenkins针对旅客数量提出了经典的ARIMA(0,1,1)(0, 1,1)12模型,并且只在模型中包含了一阶MA项和12阶MA项,我们来 看该模型的拟合效果。代码如下: proc arima data=work.air1990_2000; identify var=passengers(1 12) noprint; estimate q=(1)(12) ml; run; 模型的参数都是显著的,如图17.93所示。 图17.93 例17.18中ARIMA(0,1,1)(0,1,1)12模型参数估计报 表 残差的相关诊断显示延迟3、4、5、6没有通过白噪声检验,模型拟 合不足,仅在模型中考虑一阶MA项和12阶MA项是不够的,如图17.94 所示。 图17.94 例17.18中ARIMA(0,1,1)(0,1,1)12模型残差诊断 经过多次尝试,我们在模型中添加了一阶AR项,即模型变成了 ARIMA(1,1,1)(0,1,1)12。代码如下: proc arima data=work.air1990_2000 plots=all; identify var=passengers(1 12) noprint; estimate p=1 q=(1)(12) ml; forecast lead=24 id=month interval=month; run; 参数估计和诊断检验如图17.95和图17.96所示。所有的参数都是显 著的,并且通过了白噪声检验。 图17.95 例17.18中ARIMA(1,1,1)(0,1,1)12模型参数估计报 表 图17.96 例17.18中ARIMA(1,1,1)(0,1,1)12模型残差诊断 模型信息如图17.97所示。 图17.97 例17.18中ARIMA(1,1,1)(0,1,1)12模型信息 模型可以写成如下形式: (1-0.51775B)(1-B12)(1-B)(yt-14.37511)=(1-0.87999B) (1-0.54245B12)εt 预测效果如图17.98所示。 图17.98 例17.18中ARIMA(1,1,1)(0,1,1)12模型预测效果图 除了ARIMA过程,我们在17.3节中,介绍了ESM过程也可以对带季 节和趋势的时间序列进行建模,读者可以自行尝试。 17.5 本章小结 本章涵盖了时间序列预测的基础知识和用SAS进行时间序列分析的 一般步骤和常用技术。首先介绍了时间序列的概念、数字特征,以及常 见的平稳模型和非平稳模型,然后结合SAS的ARIMA过程,介绍了运用 Box-Jenkins方法对平稳序列建模的基本步骤。在此基础上继续介绍了如 何运用ARIMA过程进行单位根检验判断序列的平稳性,以及对趋势时 间序列进行建模的方法。此外,还通过例子介绍了如何运用FORECAST 过程、AUTOREG过程和ESM过程建立趋势时间序列模型。本章最后介 绍了运用自相关函数图和SPECTRA过程对时间序列的周期进行探索的 方法,并结合1990年到2000年美国国内航班旅客人数的实例,介绍了运 用ARIMA过程建立季节随机模型的方法。 第18章 SAS数据挖掘的一般流程 前面介绍了统计分析中常见的概念与方法。在讨论这些统计方法的 过程中,还引入了一些数据分析的例子。这些例子中要解决的问题是明 确的,大部分数据几乎不需要额外的处理就可以直接用来作为分析的输 入,显然这是比较理想的情况。在实际使用中,如何从一个商业需求出 发,明确需要解决的问题,准备所需要的数据,选择适当的分析方法, 往往是一个探索和反复的过程。由于一个完整的数据挖掘过程会包含数 据分析中大部分常见的步骤和技术,所以本章将通过讨论数据挖掘的一 般步骤,帮助读者掌握数据分析的一般过程。 18.1 SAS数据挖掘概述 所谓的数据挖掘,是指通过对大量的数据进行选择、探索与建模, 来揭示包含在数据中以前不为人知的模式或规律,从而为商业活动或科 学研究提供帮助和服务。 数据挖掘中有以下两个重要的类别: ·有监督分析(SupervisedAnalysis) ·无监督分析(Unsupervised Analysis) 有监督分析一般涉及一个或者多个目标变量。因此,有监督分析属 于目标导向(Goal Directed)型分析。常见的有监督分析包括判别分析 以及预测等,往往是根据分好类的历史数据来进行建模的。建模之后, 根据初始模型的结果,结合历史数据,对初始模型进行调整、改进,从 而得到新的模型。一般来说,这个过程不是一步完成的,是一个反复的 过程。 相反,无监督分析往往没有明确的目标变量。因此,无监督分析也 称数据驱动分析(Data Driven)。在某些情形下,甚至没有分析结果好 坏的评判标准。例如,某电信公司希望了解其手机用户的特征,例如是 否打国际长途、是否经常进行国内长途通话、每次通话的时间长度等。 在研究这些特征后,我们可以将手机用户分成若干组,但是分类方法是 否合理、是否存在更好的方法,还需要根据业务的实际情况进行进一步 考察,这就属于无监督分析的范畴。 数据挖掘的应用日益广泛。例如,在商业应用中,现在几乎每一个 银行都有信用评分系统对信用卡的发放或信用额度的申请进行管理,并 且大部分银行拥有自己的反欺诈系统,用于发现和预防恶意的欺诈行 为。在社会管理中,对犯罪活动的有效识别和预警、各种因素对环境影 响的判定和度量等都是数据挖掘的例子。 18.2 18.2.1 确定业务问题和数据准备 确定业务问题 进行数据挖掘的第一步,是要确定需要解决的业务问题。解决什么 问题、首先要解决哪些问题、在什么范围内解决多大规模的问题,都是 在数据挖掘初期需要考虑的。合理定义需要解决的问题是一个商业项目 成功的基础。在确定商业问题的过程中,涉及的主要步骤如下: ·理解业务现状。 ·提出业务问题。 ·检查数据的可行性。 在进行商业项目时,我们必须要对客户的业务有一定的了解,每个 商业领域都有其独特的一面,理解业务的现状和对未来的期望,才可能 确定数据挖掘项目的目标和优先级。 客户往往会对自己的业务活动中存在的问题和要求有一定的认识, 一般情况下,这些认识对分析人员理解实际业务会有一定的帮助,分析 人员应在理解实际业务的基础上,对这些问题和要求做出清晰的定义和 表述,并与相关人员达成共识。 在确定了商业问题后,通常就要了解是否可以获取相关的数据。这 时需要考察现存的数据仓库或者数据集市系统、交易系统以及存在于其 他外部系统中的数据等。现有的数据结构如何、是否具备该数据、当前 的数据是否包含了足够的信息,这些都将影响问题解决的效果。 为了便于讨论数据挖掘的完整过程,考虑使用一个示例贯穿本章。 假设要解决的业务问题如下: X公司经过调查发现,在A地区小商品市场发达,微型/小型商铺众 多,这些商铺经常会对短期资金有需求。但是传统大型银行的贷款业务 主要面向企业用户,并且手续繁多,微型/小型商铺(多数是以家庭为 单位经营)往往很难从大型银行得到商业贷款,而且整个贷款申请的处 理时间也相对较长。针对这一现状,X公司开展了针对上述个人商铺或 小型商铺的贷款业务。经过一段时间的尝试,该项业务的回报率相对比 较高。但也存在一些问题,主要有: ·申请人数日益增多。X公司已经无法通过人工处理的方式对贷款申 请进行逐一处理。 ·历史贷款中不良贷款比例高达20%。 18.2.2 数据准备 前面已经确定了要分析的问题,我们对需要的数据有了一定的、总 体上的认识。接下来,要进一步检查数据,看数据是否符合数据挖掘的 要求,我们将这一个过程称为数据准备。这一过程主要包括以下几个步 骤: ·创建数据挖掘的环境。 ·观察并验证数据。 ·准备数据。 从数据的来源来看,数据可以分为内部数据与外部数据。顾名思 义,它们分别指数据来源于组织内部还是组织外部。例如,示例中,X 公司的客户基本信息、贷款发放记录、客户的还款交易等,均为内部数 据。为了使得模型更加准确,有时需要将当地的人口结构、人均收入、 经济增长速度等因素也考虑进去,但这些数据并非来自于X公司内部, 因此属于外部数据的范畴。 创建数据挖掘环境的首要工作是对数据源进行确定。在确定了数据 源之后,接下来的工作是考虑如何访问和获取这些数据源中的数据。在 实际操作时,数据可能存在于不同的操作系统下,或者存在于不同的数 据库或数据仓库中,而且数据的格式一般来说也是不一样的。数据挖掘 的数据环境一般有3种: ·将数据存储在原位置,待需要时再进行访问。 ·将数据单独存储。 ·前面两种的混合。 如果条件允许,理想状态下应将数据挖掘需要的数据单独存储。 在创建完合适的数据挖掘环境后,下一步工作是检查并验证数据, 具体包括以下3个方面的内容: ·检查数据的完备性。所谓的完备性指的是数据挖掘需要用到的所 有数据都可以得到。有的数据可能没有办法直接得到,需要通过若干个 数据整合处理生成。完备性不满足不仅对数据挖掘效果有影响,在某些 情形下,甚至会影响某种数据挖掘方法的可行性。 ·检查数据的相关性。检查数据的相关性实际上是对现有数据的一 个验证过程。可以将现有的数据与目标联系起来考虑,确定每一个数据 是否都是相关的(没有冗余)。 ·确保数据的格式。数据中的某些部分可能会过于冗长,对此,在 验证数据的时候可能需要考虑简化其复杂性。此外,还应该考虑是否需 要提供额外的数据,如汇总列等。 检查并验证数据后,就是准备数据了,此过程主要包括以下3个方 面: ·清理数据。 ·去除冗余列。 ·保证数据的一致性。 这个阶段的工作是要对数据的合理性做出判定。例如,数值是否在 合理的范围内。假设数据集中的某一列代表客户的信用记录长度(单 位:年),另一列代表客户的年龄,若发现客户信用记录长度大于客户 年龄,那么就表示这两个值中至少有一个值是不合理的。如果客户的真 实年龄应该是介于18岁与55岁之间,那么我们还需要验证年龄列中是否 存在上述范围以外的值。对数据取值合理性的验证有可能是一个复杂的 过程,实际中一般采用查看统计量的方法来处理,例如,通过查看数据 的均值、最大值、最小值来判断列中是否存在不符合规定的数据。 由于数据可能来自于不同的地方,多个数据之间的信息可能会重 叠,因此,对于那些信息重叠的数据集就没有必要囊括整个数据集了, 这也是我们要对数据去除冗余的原因之一。此外,对于不同层级的汇总 数据,它们之间的信息也可能是相互包含的,这时候可以仅保留与目标 相关的部分汇总数据集。 在数据清理工作中,还要保证数据的一致性,包括变量的单位前后 是一致的、变量的意义前后没有改变等。例如,有的数据集中变量 Revenue是以亿为单位的,而有的数据集中该变量是以百万为单位的, 这就是前后不一致;又如,有的数据集中变量Profit表示的是税后利 润,而另外一些数据集中该变量表示的是税前的利润,这也属于前后不 一致。 假设我们根据要解决的业务问题将X公司业务系统中的数据进行了 抽取和清洗,得到了发放贷款的历史数据ex.smbl,该数据共包含6000条 观测、17个变量。具体变量含义如下: ·ID:经过处理后的客户ID ·PROFITRATE:资产收益率(百分比) ·BAD:是否为不良贷款(0:否,1:是) ·REASON:贷款原因/用途(0:资金周转,1:扩大规模,缺失 值) ·DELINQ:信用记录中拖欠交易次数 ·DEBTINC:店铺资产负债比率(百分比,全部负债除以全部资 产) ·EDUCATION:申请人学历(1:高中以下,2:大学,3:研究生 及以上) ·YROPEN:店铺经营时间(单位:年) ·REVENUE:店铺年营业额(单位:元) ·CREDITAGE:申请人信用记录(单位:年) ·LOCAL:是否为本地户籍(0:否,1:是) ·AGE:申请人年龄 ·RENT:店铺月租金(单位:元) ·CREDITLEVEL:申请人信用等级(1:A+,2:A,3:B+,4: B) ·STOREAREA:店铺面积(单位:平方米) ·NUMEMPLOYEE:雇员人数 ·INDAREA:所属行业(1:服务业,2:零售业,3:其他) 至此,针对X公司的现状,我们确定了要分析的问题以及所需要的 数据,接下来将针对数据进行后续的分析工作。 18.3 数据抽样、探索与加工 经过前面的步骤,已经明确了待分析的问题,确定了数据源。从这 一节开始,要对数据本身进行操作,直至数据可以用来建模。这一阶段 的工作主要包括以下几个方面的内容: ·数据抽样 ·数据探索 ·对数据进行加工 这里需要指出的是,数据探索与数据抽样这两步的先后顺序不是一 成不变的,二者是可以对调的,在某些情形下,也可能需要进行反复的 抽样与探索。 18.3.1 数据抽样 假设要对规模较大的数据(例如,数亿条观测)查看某个变量的分 布,以探索合适的数学建模方法,在这种情况下,直接对其进行操作可 能不是一个很好的选择。一个常见的方法是,先对大规模数据进行抽 样,基于样本数据进行分析。一般来说,如果样本容量足够大,就能够 保证对变量的分布有很好的估计。此外,在建模时,我们一般会建立多 个模型进行比较,并选择其中的一个模型进行参数调整,如果类似的操 作直接在原数据集上进行,可能对硬件资源的要求就会比较高。可见, 对原数据集进行抽样不失为一个好的选择。 用到数据抽样方法的另外一种情况是,数据集中的数据规模太小, 无法判断总体的分布,这时候就可以通过抽样的方法(例如,放回的无 限制随机取样,定义见下文),生成“足够多”的数据。 SAS通过PROC SURVEYSELECT实现常见的抽样方法,具体而 言,该过程步提供以下几种等概率抽样方法。 ·不放回的简单随机抽样(Simple Random Sampling):按照等概率 的原则,直接从含有N个元素的总体中抽取n个元素(不放回)作为样 本。 ·放回的无限制随机抽样(Unrestricted Random Sampling):按照等 概率的原则,直接从含有N个元素的总体中抽取n个元素(放回)作为 样本。 ·系统随机抽样(Systematic Random Sampling):先将数据集中的 观测按照某种规则进行编号,记编号为1~N,假设抽样的样本容量为 n,那么抽样距离为K=N/n,抽样时,先在1-K中随机选取一个观测i,接 着选取i+K、i+2K、i+3K等,以此类推,直至得到n条观测为止。 ·逐次抽样(Sequential Random Sampling):开始只抽取少量的样 本来决定是否接受某一假设,或者继续抽取样本,直至能够接受或拒绝 该假设为止。 ·伯努利抽样(Bernoulli sampling):该抽样方法是一个等概率方 法,但是每次抽样结果的样本容量不是固定的。总体中的每一个个体是 否被抽取,都可以看成是一个伯努利试验。 此外,PROC SURVEYSELECT还提供以下几种比例抽样 (Probability Proportional to Size Sampling,PPS抽样)方法: ·PPS无放回抽样(PPS Sampling Without Replacement) ·PPS放回抽样(PPS Sampling With Replacement) ·PPS系统抽样(PPS Systematic Sampling) PROC SURVEYSELECT的一般形式如下: PROC SURVEYSELECT 选项; CONTROL 变量; FREQ变量 ; ID变量 ; STRATA变量<选项> ; RUN; 其中: ·PROC SURVEYSELECT语句常见的选项如表18.1所示。 ·如果用户使用CONTROL语句,那么PROC SURVEYSELECT在进 行抽样前会将数据按照CONTROL语句中指定的变量排序。 ·FREQ指定一个数值变量,该变量的值代表每条观测出现的频数。 ·ID语句指定若干个(原始数据集中的)变量,这些变量将被囊括 进指定的输出变量数据集(使用“OUT=”选项指定,具体见表18.1)。 ·STRATA语句指定用来分层的变量。PROC SURVEYSELECT从这 些层中独立地进行抽样。 表18.1 PROC SURVEYSELECT常见的选项 有关PROC SURVEYSELECT更加详细的介绍可以参考SAS帮助文 档。 18.3.2 数据探索 对数据进行探索首先要对数据有一个“整体”的认识,例如有多少个 观测、多少列,以及每列的取值特征等。此外,还可以结合变量的汇总 统计信息对每个变量的分布进行观察。通过数据探索可以了解变量取值 变化的趋势、确定数据中异常的观测。 常见的用于数据探索的方法有计算汇总统计量与可视化(如直方 图)等。此外,也可以借助于其他统计方法,如变量之间的关联性分 析、变量聚类分析[1]等,实现对数据集的初步分析。 1.“熟悉”数据集本身结构 数据探索的第一个工作是要“熟悉”数据集本身的结构。首先,需要 了解的问题如下: ·数据集有几个观测?是否有重复的观测?不重复的观测的数目是 多少? ·数据集有多少列?列的名称是什么?标签是什么? ·数据集是否已经排序?按哪一个或者哪几个变量排序? ·数据集是否包含索引? ·数据集是否包含缺失值? 解决上述问题常见的工具有: ·SAS资源管理器(SAS EXPLORER) ·PROC CONTENTS 在图18.1中,右击“SAS资源管理器”中的数据集打开其属性对话 框,在属性对话框上切换不同的标签(如“列”、“详细信息”等)就可以 查看该数据集观测数、列的名称、类型、格式与标签等数据集结构信 息,限于篇幅这里就不一一展示了。 图18.1 数据集ex.smbl的属性 通过“资源管理器”查看到的信息也可以通过PROC CONTENTS 在“输出”窗口查看。代码如下: proc contents data = ex.smbl; run; 如果不仅想要查看某个数据集包含多少条观测,还想要了解这些观 测是否重复、不重复的观测有几条、是否包含缺失值等,也都可以通过 PROC SQL来实现。具体的操作读者可以查阅本书SQL语言中有关 COUNT函数的使用。 2.通过汇总信息对数据集进行探索 前面介绍的数据探索都是从数据集本身的结构入手的。接下来,将 深入数据集中的列,对列进行“探索”。 一个数据集往往会包含多列。在数据挖掘中,列被称为变量,并且 根据变量在建模中的作用,又将其分为输入变量(自变量)与目标变量 (因变量)。这里需要注意的是,如果使用的是无监督分析,目标变量 是不存在的。例如,在对变量进行聚类时,所有的变量都是输入变量, 不存在目标变量。 根据变量的取值属性,可以将其分为连续变量与离散变量。其中, 离散变量又可以进一步分为定类变量、有序变量、0-1变量(0-1变量属 于定类变量,由于其特殊性,一般独立列出)、单维变量(Unary Variable,单位变量中,变量的取值为唯一,且为固定值,单维变量在 建模中一般用处不大)。对于连续型变量,可以查看其集中趋势 (Central Tendency)、离散程度以及分布情况(相应的统计量如表18.2 所示);对于离散变量,可以统计出不同取值的个数、比例,也可以画 出直方图。 表18.2 连续型变量中部趋势、离散程度、分布以及相应的统计量 有关描述性统计量的概念以及如何利用SAS实现,可以参考本书第 9章“描述性统计分析”。 例18.1:数据集ex.smbl中包含了17个变量,该历史数据将用于建模 来判定新的申请者是否有可能为不良贷款。 首先,可以通过查看数据集,确定其中每个变量的角色与具体类 型。在数据集变量中,除了ID变量外,其余变量的角色与类型如表18.3 所示。 表18.3 数据集ex.smbl中变量角色与类型分析 其次,需要了解每个变量是否含有缺失值以及缺失值的个数,代码 如下: proc means data = ex.smbl N NMISS; var _numeric_; run; 图18.2 数据集ex.smbl变量及其包含缺失值的个数 代码的输出结果如图18.2所示。 从上述的输出结果可以看出:变量DELINQ、DEBTINC、 REVENUE、CREDITAGE、AGE以及REASON包含有缺失值(对于缺 失值的处理会在下面讲述)。 对于离散的变量(包括定类变量与有序变量),可以查看其不同取 值的频数分布图。代码如下: %macro FreqBar(ds, varname); proc freq data=&ds; tables &varname / plots(only)=freqplot; run; %mend; %FreqBar(ex.smbl, local) %FreqBar(ex.smbl, creditlevel) %FreqBar(ex.smbl, education) %FreqBar(ex.smbl, bad) %FreqBar(ex.smbl, indarea) %FreqBar(ex.smbl, reason) 提交上述代码,输出结果如图18.3所示。 对于连续变量,可以进一步查看其最小值、最大值、均值以及中位 数等信息。示例代码如下: proc means data = ex.smbl N nmiss min mean median max std; var age creditage delinq debtinc numemployee profitrate rent revenue storearea yropen; run; 图18.3 数据集ex.smbl离散型变量频数图 运行上述代码,输出结果如图18.4所示。 图18.4 数据集ex.smbl连续性变量相关汇总统计量 3.变量的选择 在数据探索中,另外一个重要的步骤是找出“最重要”的变量。虽然 前面介绍了如何结合商业目标对变量进行初步的筛选,但那仅仅是从经 验与直观上进行的变量选择,缺乏数学与统计学分析依据。经过初步筛 选后的数据集可能仍然包含着众多变量,在这些情形下,仍然需要对变 量进行进一步的筛选。 进行数据挖掘时,用于变量筛选的常见方法有以下几种: ·逐步回归变量选择法 ·主成分法 ·变量聚类法 ·变量与目标变量之间的关联性(Association)分析法 首先考虑逐步回归变量选择法。该方法常见于回归类统计分析方 法,例如在回归分析中,变量的选择方法有3种:向前选择法、向后消 除法以及逐步回归法。这3种方法对变量进行选择的依据是变量的显著 性。 主成分法指的是从现有的众多变量中,得出若干个起主导作用的综 合指标(即主成分),然后使用这些综合指标代替原数据集中的变量进 行研究。有关主成分法的原理、思想以及用法参见本书主成分分析部 分。 变量聚类法实际上是将前面介绍的聚类分析方法用于变量,即将众 多变量分成若干类,从每一类中挑出一个“代表”进行分析。这样做的好 处是在保留原始数据集中绝大多数变量信息的基础上,尽可能减少待分 析的变量个数。事实上,变量的聚类也常用于聚类分析的数据集预处 理。 在SAS中,变量的聚类可以通过PROC VARCLUS实现,其使用语 法如下: PROC VARCLUS DATA = 数据集<选项>; BY 变量; VAR变量; RUN; 其中: ·PROC VARCLUS语句常用的选项有,MAXCLUSTERS=选项用于 指定最多可生成的类数;MAXITER=选项用于指定过程的最大迭代次 数;MAXEIGEN=选项指定一个最大特征值,当某一类中的第二大特征 值大于该指定值时,过程将对该类进行拆分;PROPORTION=选项指定 用于拆分类的解释比例阈值,PROC VARCLUS表示选择解释比例最小 的一类进行拆分,需要注意的是,如果该选项和选项MAXCLUSTERS= 同时出现,选项MAXCLUSTERS=会优先起作用。 ·使用BY语句的前提是要求数据集已经按照BY语句中的变量顺序排 序,对于数据集中的每一组观测,PROC VARCLUS都会进行聚类分 析。此外,若用户指定的BY变量多于一个,PROC VARCLUS将仅使用 最后一个变量。 ·VAR语句指定用来聚类分析的变量。 有关该过程步的具体介绍参见SAS帮助文档,这里不具体展开。 除了上述3种方法外,还可以通过考虑变量与目标变量之间的关联 性分析来对变量进行筛选。例如,在目标变量为0-1的LOGISTIC回归模 型中,可以通过计算卡方值来判断数据集中的每一个变量与目标变量之 间是否存在关联性,并通过CRAMER'S V值判断关联性强弱。若考虑单 个变量与目标变量之间的关联性,这时CRAMER'S V的值将介于-1与1 之间,其绝对值越大,关联性越强;若考虑多个变量与目标变量之间的 关联性,这时CRAMER'S V的值将介于0与1之间,该值越大,关联性越 强。 例18.2:对数据集ex.smbl中变量进行选择。 首先,根据图18.2中缺失值的信息可以看到,变量REASON和变量 DEBTINC的缺失值均比较多。由于变量DEBTINC反映的是资产负债 比,理论上是一个比较重要的贷款依据,因此,要予以保留,在后期中 会对缺失值进行处理。至于变量REASON,来看看要对它如何选择。以 下代码输出变量REASON与BAD的频数表: proc freq data = ex.smbl; tables reason*bad; run; 输出结果如图18.5所示。 图18.5 变量REASON与BAD的频数表 从图18.5中可以看到,对于REASON变量有以下结论: ·缺失值较多,约占1/3。 ·在所有非缺失值的观测中,REASON取值为0与为1的约各占一 半。 ·在BAD=1(不良贷款的观测中),REASON=0与REASON=1的观 测的比例为384∶398,大约为1。换句话说,在不良观测的贷款中,该 变量不具有很好的区分性。 综合上述几点,在后续建模中,对变量REASON,将不予以考虑。 除了变量REASON外,在其余的分类变量中,缺失值所占的比例都 很小。我们可以考虑将剩余的分类变量与目标变量BAD进行关联性分 析,计算其卡方值以及CRAMER'S V值。代码如下: proc freq data = ex.smbl; tables (Creditlevel Education IndArea Local)*Bad/chisq nocol nopercent; run; 上述代码的输出中,包括上述4个变量与BAD的交叉频数表,以 及“BAD-变量”统计量表,具体如图18.6所示。 图18.6 数据集ex.smbl中离散变量与目标变量的关联性分析 从图18.6可以看出,变量CREDITLEVEL、EDUCATION与目标变 量BAD的关联性较强,变量INDAREA以及LOCAL与变量BAD的关联性 不强。因此,考虑将变量LOCAL与BAD也排除在建模的变量之外。此 外,从CRAMER'S V值可以看出,CREDITLEVEL与BAD的关联性较 EDUCATION与BAD间的关联性强。 最后,考虑数据集ex.smbl输入变量中剩余的连续变量。对于连续变 量,可以考虑将数据集按照连续变量逐个进行排序、分组(如分成10个 组),并查看每个组内不良贷款所占的比例。 ·如果不同组中BAD=1所占的比例类似,说明该连续变量的取值对 BAD的区分性不是很好,可以考虑将该变量排除。 ·如果不同组中BAD=1所占的比例差别较大,则应考虑变化的趋势 是否合理。 具体的代码如下: %macro plottrend(ds, varname, obsingroup); data temp; *生成一个仅包含待分析变量的子数据集; set &ds(keep = &varname BAD); run; proc sort data = temp out = tmp; *对子数据集进行排序; by &varname; run; data tmp; *对排序完的子数据集进行分组标记; set tmp; group = ceil(_N_ /&obsingroup); run; data plot; *根据分组标记,对每组内的观测求平均,将平均值存储为新的数据集plot; set tmp; by group; if first.group then sum =0; if last.group then do; avg = (sum/&obsingroup); output; end; else sum + BAD; run; proc sgplot data = plot; *画图; title "&varname - Bad Trend"; series x= group y = avg / markers; run; %mend; %plottrend(ex.smbl, age, 1200) %plottrend(ex.smbl, yropen, 600) %plottrend(ex.smbl, revenue, 600) %plottrend(ex.smbl, rent, 600) %plottrend(ex.smbl, debtinc, 600) %plottrend(ex.smbl, delinq, 600) %plottrend(ex.smbl, profitrate, 600) %plottrend(ex.smbl, creditage,600) %plottrend(ex.smbl, storearea,600) 代码的输出结果如图18.7所示。 图18.7 “输入变量—目标变量”趋势图 图18.7 (续) 从图18.7可以看出,输入变量不同组在目标变量上都具有不同程度 的区分性。这时,还应考虑趋势是否正确。例如,一般来说,资产负债 率越高越容易违规,但是从debtinc-BAD趋势图看,似乎资产负债率低 的违规更多。通过对数据进行进一步观测,可以发现,头两个组,都是 缺失值。因此,该趋势图初期BAD=1的观测较多是由于Debtinc为缺失 值造成的。同理,可以对其余变量的取值趋势进行分析。 18.3.3 数据加工 前面介绍了如何对数据进行探索。从本节开始,将讨论如何对数据 集进行加工,数据加工包括: ·原始数据集的分割 ·缺失值的处理 ·变量的转化 1.分割原始数据集 在数据挖掘中,一般会将原始数据集分成若干子数据集,这些子集 互不相交(即任意两个子数据集都不包含原始数据集中的同一条观 测)。具体地说,原始数据集可以分割为以下几种子数据集: ·训练数据集。所谓的训练数据集指的是原始数据集中用来创建模 型的子数据集。 ·验证数据集。该数据也是原始数据集的一个子集,在模型创建 后,可以用这部分数据进行验证,如果模型效果不佳,则需要对模型进 行重新训练。 ·测试数据集。在建模过程中,不需要用到测试数据集。当有多个 模型进行比较时,可以使用测试数据集。由于建模阶段没有用到测试数 据集,因此在这个基础上进行模型优劣的比较会相对客观。 一般来说,训练数据集、验证数据集与测试数据集三者的占比为 35%、35%与30%。但在实际中,常常仅将原始数据集划分成70%的训 练数据集与30%的验证数据集。 例18.3:将数据集ex.smbl划分成两个子数据集,即70%的训练数据 集与30%的验证数据集。 示例代码如下: %macro partition(train_percent, validate_percent); proc sql noprint; select count(ID) into: TotalObs from ex.smbl ;quit; %let train_obs = %sysevalf(&train_percent*&TotalObs); *训练数据集观测数; %let validate_obs = %sysevalf(&validate_percent*&TotalObs); *验证数据集观测数; proc surveyselect data = ex.smbl out = split seed = 9999 group = (&train_obs, &validate_obs); run; data smbl_train smbl_validate ; set split; if GroupID =1 then do; drop GroupID; output smbl_train; end; else do; drop GroupID;output smbl_validate; end; run; %mend; %partition(0.7, 0.3) 运行上述程序,在“资源管理器”中可以查看到新生成的子数据集, 如图18.8所示。 图18.8 数据集ex.smbl的划分 2.缺失值的处理 对缺失值的处理也是数据挖掘中的难点之一。如果一条观测在某一 个变量上的取值为缺失值,那么称该观测为不完整观测。SAS中的多数 过程步都不会考虑不完整观测。虽然这样处理比较方便、简单,但是也 存在一定的问题,例如,不完整观测虽然在某个变量上的值缺失,但是 在其余变量上的信息仍然是有用的。 对取值为连续型的变量,缺失值的处理方法一般有以下几种: ·指定默认值。 ·平均值。 ·中位数。 ·(最大值+最小值)/2。 ·最小值。 ·最大值。 ·不做处理。 对取值为离散的分类变量,缺失值的处理方法一般有以下几种: ·指定默认值。 ·众数。在变量的所有可能取值中,最经常出现的一个。 ·最小值。 ·最大值。 ·不做处理。 例18.4:对数据集ex.smbl中的缺失值进行处理。回顾之前对数据集 ex.smbl的探索,可发现数据集ex.smbl共有6个变量包含缺失值,这6个 变量分别是:DELINQ、DEBTINC、CREDITAGE、AGE、REVENUE 以及REASON。其中,我们通过分析将REASON变量排除在用于建模的 变量之外。因此,现在仅需要对其余5个变量进行缺失值的处理。首先 考虑变量DEBTINC和变量DELINQ。 回顾变量DEBTINC、变量DELINQ与目标变量的趋势图,如图18.9 所示。 图18.9 “DEBTINC—BAD”以及“DELINQ—BAD”趋势图 变量DEBTINC表示的是资产负债比率,一般来说,该值越大,为 不良贷款的可能性就相对越大。上述趋势图也验证了这一点:由于数据 是按照DEBTINC值排序的,group值越大,说明DEBTINC值越大,抛开 前两组不论(这两组中该值均为缺失值),数据从第8组到第10组之间 有明显的上升趋势,第10组的不良贷款比例最高。鉴于此,在对前两组 观测该变量的缺失值进行填补时,可以考虑将DEBTINC的值设置得相 对大些。这里,将缺失值设为第8组到第10组中的某一个值(这些组上 不良贷款的比例相对较高)。 变量DELINQ表示的是信用记录中拖欠交易的次数。从总体上看, 趋势图中,group的值越大(拖欠次数越多),变量BAD取值为1的观测 比例也越大。因此,在对变量DELINQ进行缺失值填补时,可以同样考 虑将DELINQ的值设置得相对大些。这里,将缺失值设为第8组到第10 组之间的某一个值。 对于剩余两个变量,即CREDITAGE与AGE,使用均值填补缺失值 即可。 为此,分别计算出这几个统计量。代码如下: proc means data = ex.smbl n nmiss mean p90 ; var debtinc delinq creditage age revenue; run; 提交上述代码,输出结果如图18.10所示。 图18.10 部分还缺失值变量的汇总统计信息 根据上面的分析及图18.10,可以对数据集中变量的缺失值进行填 补。示例代码如下: data smbl_train_impute; set smbl_train; if (debtinc = . )then debtinc = 41.78; if (delinq = .) then delinq = 2; if (creditage = .) then creditage =6.9; if (revenue = .) then revenue = 100349; drop indarea local reason; run; if (Age = .) then Age = 39; data smbl_validate_impute; set smbl_validate; if (debtinc = . )then debtinc = 41.78; if (delinq = .) then delinq = 2; if (creditage = .) then creditage = 6.9; if (Age = .) then Age = 39; if (revenue = .) then revenue = 100349; drop indarea local reason; run; 经过缺失值填补的数据集smbl_train_impute以及数据集 smbl_validate_impute如图18.11所示。 图18.11 数据集smbl_train_impute以及数据集smbl_validate_impute 3.变量的转化 在数据加工中,变量的转化也是一个重要的环节。对于连续变量, 常见的转换方法有以下几种: ·简单变形(Simple Transformations)。常见的对输入变量进行简单 变形的方法有,LOG、开方根、取逆、平方、取指数以及标准化等,通 常使用变换后的新值来代替原来的输入变量进行分析。例如,在 LOGISTIC回归中,理想情况下,LOGIT(因变量)与其他变量(自变 量)之间呈线性关系。假设现有一个变量与LOGIT之间呈二次关系,在 这种情况下,可以考虑对变量进行平方变形,将平方后的变量与原变量 一起纳入建模分析的变量中。 ·BUCKET转换法。该方法可以将连续变量转化为分类变量,具体 做法是,将最小值与最大值之间的区间分成长度相等的n组,任何两组 之间不包含同一条观测,且每一条观测恰好落入其中的某一组中。例 如,将年龄分成0~25、25~50、50~75以及75~100四组,每一组所包含的 观测数可能不一样。 [1] 聚类的结果是将原数据集中的变量分成若干类,从每一类中选出一 个“代表”,用其代替类中的所有变量。这样处理后变量的个数就大大减 少了。 18.4 数据建模 经过前面一系列的数据抽样、探索与加工后,我们对数据有了相当 的了解,而且数据经过处理后,已经可以用来进行建模了。建模的过程 包含模型的训练、模型的验证与模型的测试3个步骤,这3个步骤分别对 应着3个数据集:训练数据集、验证数据集与测试数据集。模型经过训 练与验证后,可对模型的参数进行适当的调整,以得到更好的模型。模 型的训练与验证可能是一个反复的过程。模型经过训练、验证、调整直 至确定,这一过程用到的数据集为训练数据集与验证数据集,并不会涉 及测试数据集。不过,在上述过程中,可能会产生多个不同的模型,在 这种情况下,如果存在测试数据集,可以用其对模型进行测试,根据这 个结果来衡量模型的优劣。 18.4.1 模型的建立 模型方法的选择与输入变量以及目标变量的类型是紧密相关的。例 如,如果输入变量是分类变量、目标变量是连续的,那么就可以考虑使 用方差分析;如果输入变量是分类变量、目标变量也是分类变量的情 形,则可以考虑使用LOGISTIC回归;如果输入变量既包含分类变量也 包含连续变量,而目标变量为分类变量,也可以考虑使用LOGISTIC回 归;如果输入变量既包含分类变量也包含连续变量,目标变量为连续变 量,则应考虑协方差分析(这部分内容不是本书范畴,有兴趣的读者可 以自行查阅相关文档)。 一般来说,对于同一个数据集可以采用多种方法建立模型,然后比 较多个模型拟合数据的效果,从中选择出一个最好的方法。即使是采用 某一特定的方法建立起来的模型,也需要不断试验,调整模型中的参 数。总之,模型的建立与调试可能是一个反复试验的过程。 例18.5:对数据集smbl_train_impute进行建模分析。数据集 smbl_train_impute是从原始数据集ex.smbl中拆分出来的子数据集,已经 过缺失值处理,用于模型训练。数据集smbl_train_impute中的目标变量 是0-1变量,输入变量包含连续变量与分类变量,根据这些特征,可以 考虑采用LOGISTIC回归进行建模分析。 基于业务经验,X公司的业务人员认为申请贷款人的信用程度、拖 欠贷款的次数、店铺的营业额、盈利程度以及店铺存在的时间等因素为 决定是否发放贷款的主要因素。据此,我们可以建立一个LOGISTIC模 型。代码如下: proc logistic data = smbl_train_impute desc ; class creditlevel ; model bad = creditlevel delinq debtinc profitrate /selection = none; run; revenue yropen 运行上述代码,输出汇总信息,结果如图18.12所示。 图18.12 模型输出(一) 紧接着,输出的是以下内容(如图18.13所示): ·“分类水平信息”表,该表对应代码中的CLASS语句。使用 OUTDESIGN时会用到该表格,具体见下文。 ·“模型收敛状态”表,从该表可以看出该模型是收敛。 ·“模型拟合统计量”表,当有多个LOGISTIC模型时,该表中的统计 量越小,说明拟合程度越好。 ·“检验全局零假设”表,若接受该假设检验,说明模型中的变量对 目标变量的分类几乎没有作用。从图18.13中该表的结果可以看出,模 型中的变量对目标变量的分类是有作用的。 ·“3型效应分析”表,该表的卡方值均小于0,说明模型中的变量对 目标变量的区分都是有一定程度的作用的。从这个角度看,也验证了业 务人员的判断。 其余部分的输出如下(如图18.14所示): ·“最大似然估计分析”表,该表实际上是对模型数学表达式中各个 参数的估计。列“Pr>卡方”用于检验该变量在LOGISTIC回归模型中是否 显著。从图18.14中显示的表可以看出,在CREDITLEVEL取值为2时, 列“Pr>卡方”取值较大,因此,可以认为其对模型的贡献不大,需要做 进一步的处理,具体操作下面会讲述。 ·“优比估计”表,给出效应的点估计以及它们对应的95%的置信区 间。 ·“预测概率和观测相应的关联”表,该表反映的是预测的准确度。 图18.13 模型输出(二) 图18.14 模型输出(三) 在LOGISTIC回归模型中,可能存在某个分类变量下的若干水平, 在这些水平下,因变量的取值区分并不明显,比如,变量 CREDITLEVEL中取值为2的水平。在这种情况下,应该将不明显的水 平排除在模型外,这个过程可以通过OUTDESIGN选项实现。 OUTDESIGN选项会将分类变量的水平输出成为新的变量,新变量的个 数与原分类变量的自由度相等。 例18.6:对上述例子中的模型进行调整,将CREDITLEVEL取值为2 的水平排除在模型外。 首先,使用OUTDESIGN选项将分类变量输出成为新的变量。具体 代码如下: proc logistic data = smbl_train_impute desc outdesign = work.design; class creditlevel ; model bad = creditlevel delinq debtinc profitrate revenue yropen /selection = none; run; 运行上述代码,输出结果与上例中的输出基本一致,这里就不重复 介绍了。输出结果中的“分类水平信息”表如图18.15所示。图中的设计变 量即为新生成的变量。可以看出,如果原数据集中的某一观测,其 CREDITLEVEL的取值为1,那么在新的数据集work.design中,新生成的 变量取值依次为:1、0、0。 图18.15 分类水平信息表 此外,生成的数据集work.design如图18.16所示。 图18.16 数据集work.design 接着,可以使用work.design代替原来的训练数据集进行回归模型的 建立。下面对上述代码进行修改: ·删除分类变量CREDITLEVEL。 ·输入变量中增加两个新的变量,即CREDITLEVEL1和 CREDITLEVEL3。 新代码如下所示: proc logistic data = work.design desc ; model bad = creditlevel1 creditlevel3 delinq debtinc yropen /selection = none; run; profitrate revenue 运行上述代码,除汇总信息外,其他部分的输出结果如图18.17所 示。 图18.17 经过调整后的模型输出结果 从图18.17可以看出,经过调整后,模型中对因变量的预测均具有 显著作用(“Pr>卡方”列最大的为0.0129,小于0.05的默认值)。因此, 我们可以选择该模型作为初步模型,进一步用于下一步的模型评估。 18.4.2 模型的评估 事实上,在上一步模型的建立过程中,已经对模型进行了评估:我 们发现分类变量CREDITLEVEL中有一个水平在模型中并没有显著性作 用,因此对模型进行了调整,调整后的模型不包含该水平。本节讲述的 模型评估是使用验证数据集评估由训练数据集建立起的模型。一个好的 模型不仅会在训练数据集上有很好的拟合、分类效果(这里以因变量是 离散的分类模型为例),还要求在验证数据集上也有类似的效果。若模 型在验证数据集上的分类效果欠佳,就需要考虑重新回到训练数据集 上,对模型的参数进行调整(甚至可以尝试不同的建模方法)和验证。 因此,从这个角度看,模型的建立与评估可能是一个反复的过程。 评估一个模型好坏的方法有很多。以前面建立起来的LOGISTIC模 型来说,可以从以下几个方面对模型进行评估: ·统计预测结果。统计验证数据集中目标变量的取值(已知)同模 型预测出来的目标变量的取值之异同数,并计算出一个预测正确或者错 误的比例。 ·LIFT图。LIFT图可以直观地显示使用预测模型相对于不使用(随 机猜测)的改进。当存在多个模型时,可以通过LIFT对比哪个模型更 好。 例18.7:对模型预测结果进行统计,评估前面建立的LOGISTIC回 归模型。 在使用训练数据集work.design进行建模时,我们对代码进行了稍微 的修改,包括:使用关键字OUTMODEL将模型输出;使用SCORE选项 进行打分。修改后的代码如下: proc logistic data = work.design desc outmodel = work.estimate1 ; model bad = creditlevel1 creditlevel3 delinq debtinc profitrate yropen /selection = none; score data= work.design out=scores1; run; revenue 运行上述代码,除了生成代码修改前的输出结果外,还生成了数据 集work.estimate1以及work.scores1。其中,数据集work.estimate1保存的 是模型的信息,可以在其他的LOGISTIC过程中加以调用。数据集 work.scores1如图18.18所示,该数据集除包含原始数据集的变量外,还 包含以下几个新的变量: ·F_BAD(标签:“从:BAD”)。与BAD的取值一样,即原数据集 中观测所属的类。 ·P_0(标签:“预测概率:BAD=0”)。对于原始数据集中的每一条 观测,根据模型可以计算出该观测属于“BAD=0”的概率。 ·P_1(标签:“预测概率:BAD=1”)。对于原始数据集中的每一条 观测,根据模型可以计算出该观测属于“BAD=1”的概率。 ·I_BAD(标签:“到:BAD”)。根据模型判读出来观测所属的类 别(判断的依据正是P_0与P_1的大小)。 现欲将该模型运用到验证数据集smbl_validate_impute上。由于模型 中包含了变量CREDITLEVEL1和变量CREDITLEVEL3(由原分类变量 CREDITLEVEL的各个水平转化而来的),为此,需要对验证数据集中 的CREDITLEVEL进行同样的操作。具体代码如下: proc logistic data = work.smbl_validate_impute desc outdesign = work.design2; class creditlevel; model bad = creditlevel delinq debtinc profitrate revenue yropen /selection = none; run; 图18.18 数据集work.scores1 新生成的数据集work.design2包含了使用验证数据集进行建模所需 要的全部变量,可以使用回归模型(对应关键字INMODEL=)对其进行 打分。代码如下: proc logistic inmodel=work.estimate1 ; score data= work.design2 out=work.scores2; run; 接下来,就可以分别统计模型在训练数据集smbl_train_impute以及 验证数据集smbl_validate_impute上的预测分类效果了。具体代码如下: proc freq data = work.scores1; title '数据集smbl_train_impute的预测分类效果'; table F_BAD*I_BAD; run; proc freq data = work.scores2; title '数据集smbl_validate _impute的预测分类效果'; table F_BAD*I_BAD; run; 运行上述代码,输出结果如图18.19所示。 从图18.19中可以看出,模型在训练数据集smbl_train_impute以及验 证数据集smbl_validate_impute上的“表现”是类似的。以两个表格的 F_BAD取值为0的单元格为例,从“F_BAD=0”到“I_BAD=0”:在数据集 smbl_train_impute中,有3341条观测变量BAD的取值为0,经过模型计算 后,有3247条观测的归类是正确的,所占的比例为97.19%,此外,有94 条观测的归类是错误的,所占的比例为2.24%。对比验证数据集 smbl_validate_impute,共有1464条观测变量BAD的取值为0,根据模型 预测,有1427条观测的归类是正确的,所占的比例为97.47%,此外,有 37条观测的归类是错误的,比例为2.53%。从预测分类效果角度看,由 训练数据集得到的模型在验证数据集上有着类似的效果,即模型适用于 验证数据集。 图18.19 模型在训练数据集以及验证数据集上的预测分类效果对比 接下来,我们从另外一个角度,即LIFT图,来评估前面建立的 LOGISTIC模型。从前面的数据集work.scores1中,我们可以看出每一条 观测都有一定的概率属于“BAD=1”(即变量BAD的取值为1),其中, 变量为P_1的取值即为概率的大小。我们可以将数据集work.scores1按照 变量P_1的取值从大到小进行排序,将数据集分成若干个组,这里我们 将数据集分为10组。考虑第1组,该组内变量P_1的取值较大。如果模型 是准确的,那么实际中,该组中BAD取值为1的观测比例也应该较多, 记该比例为p1。如果不使用模型预测(随机预测),那么BAD取值为1 的概率p2应该与原始数据集中BAD的比例一致,即 859/4200=0.2045(见图18.18)。现考虑 组内的预测效果越好。 该值越大,说明模型在该 与第1组相反,在第10组中,变量P_1的值较小,因此比例的取值应 该也很小,相应的,整个p值也会较小。据此可以推测,一个好的模 型,其p值应该是呈现出逐渐下降的趋势。将上述所有组的p值用二维图 的形式表示出来就形成了LIFT图。 例18.8:使用LIFT图评估前面建立的LOGISTIC回归模型。 首先考虑训练数据集的LIFT图。训练数据集对应的得分数据集为 work.scores1,先将得分数据集按照变量P_1的值进行降序排列,排序后 的数据集为work.sorted_s1。接着,对数据集work.sorted_s1中的观测进 行分组标记,每420条为一组,共10组,标记后的数据集为work.temp。 然后,计算数据集work.temp中的每一组对应的p值,共10组,然后将p 值输出到数据集work.plot。最后,对数据集work.plot进行作图,得到 LIFT图。具体代码如下: proc sort data = work.scores1 out = work.sorted_s1; by descending P_1; run; data work.temp; set work.sorted_s1; group = ceil(_N_ /420); run; data work.plot; *根据分组标记,对每组内的观测求p值; set work.temp; by group; if first.group then sum =0; if last.group then do; avg = (sum/420); p = (100*avg)/(859/4200); output; end; else sum + BAD; run; proc sgplot data = work.plot; *画图; title "训练数据集LIFT图"; series x= group y = p / markers; run; 同样地,可以根据数据集work.score2作出验证数据集的LIFT图。具 体代码如下: proc sort data = work.scores2 out = work.sorted_s2; by descending P_1; run; data temp; set work.sorted_s2; group = ceil(_N_ /180); run; data plot; *根据分组标记,对每组内的观测求p值; set temp; by group; if first.group then sum =0; if last.group then do; avg = (sum/180); p = (100*avg)/(336/1800); output; end; else sum + BAD; run; proc sgplot data = plot; *画图; title "验证数据集LIFT表"; series x= group y = p / markers; run; 运行上述两段代码,输出结果如图18.20所示。 从LIFT图中可以看出: ·模型从左到右呈下降趋势,使用模型比不使用模型的效果要好。 例如,从训练数据集的LIFT图可以看出,在第1组,p值约为480,说明 使用模型辨别BAD=1的效率是不是用模型效率的4.8倍。 ·训练数据集的LIFT图与验证数据集的LIFT图趋势一样,形状类 似。这说明,由训练数据集建立起来的模型也适用于验证数据集。这与 我们先前根据统计分类效果得出的结论是一致的。 图18.20 训练数据集LIFT图与验证数据集LIFT图对比 综合例18.5至例18.8,从模型的拟合效果、数据的预测效果可以认 为该模型适合用于下一步实施。 18.4.3 模型的实施 经过模型评估阶段,模型已经最终确定下来,确定下来的模型可以 进一步用于实施了。回顾前面的LOGISTIC回归模型(图18.17),模型 中参数的最大似然估计分析如图18.21所示。 图18.21 LOGISTIC回归模型的最大似然估计分析 从图18.21可以得到模型的数学表达式如下: 其中, 公式中的p值为观测属于“BAD=1”的概率。在实际应用中,用这个 数学表达式对新的数据集(目标变量取值未知)进行计算,对于每一个 新增的贷款申请,可以计算出该申请属于“BAD=1”的概率值p,进而根 据p值大小决定是否发放贷款。 18.5 本章小结 本章介绍了SAS数据挖掘的一般流程:从确定商业问题、准备数 据、对数据进行探索加工,到模型的建立、评估与实施。为了使读者更 容易理解这一过程,我们使用了一个例子贯穿整个过程。这个处理过程 分布于数据挖掘的流程中,表18.4总结归纳了整个例子。 表18.4 本章例子的归纳、总结 需要指出的是,并不是每一个项目都必须经过或局限于上述步骤, 步骤之间的顺序也是可以灵活调整的。 第三篇 SAS优化建模 第19章 运筹学概述 第20章 线性规划 第21章 运用PROC OPTMODEL建立线性规划模型 第22章 PROC OPTMODEL程序设计 第23章 整数线性规划和混合整数线性规划 第24章 优化建模实例 第19章 运筹学概述 运筹学是应用恰当的科学技术知识和数学方法对实际问题进行研 究,从而提供量化分析依据以及最优方案,帮助决策的一门学科。本书 第2篇介绍的统计分析方法,可以分析我们关心的事件“正在发生什 么”、“接下来出现的会是什么样”和“按照现在的趋势发展将来会怎么 样”。而现在要介绍的,则是如何运用运筹学的知识对未来会发生的事 情进行规划,作出最优决策,做到“让未来最好”。我国古代就有“运筹 帷幄、决胜千里”之说,著名的例子有“田忌赛马”、“围魏救赵”、“丁渭 修宫”等。 在国外,运筹学被称为Operations Research,简称OR,其思想可以 追溯到19世纪中期,第二次世界大战以后得到了广泛的应用。运筹学的 发展几乎与计算机技术同步。20世纪60年代初,由大规模集成电路作为 元件的计算机的问世,使得求解各种复杂的数学模型成为可能,这为运 筹学赢得了迅速发展的黄金时期。如今,运筹学已经进入大系统问题时 代。作为一个优秀的运筹工作者,不仅要具备分析问题和解决问题的能 力,还要尽可能多地掌握现有的各种应用数学方法,而且要能熟练使用 运筹优化软件,这样,才能在面对实际问题时游刃有余。 本章将介绍运筹学的一些基本概念、在运筹学中处于重要地位的线 性规划问题和整数规划问题的基本理论,以及如何运用SAS/OR软件对 这类问题进行求解。 19.1 运筹学发展简介 1.发展历史 运筹学的朴素思想,可以追溯到公元前400年前中国古代著名军事 学家孙武的《孙子兵法》中有关思想的描述。作为中国古代运筹思想的 先行者和实践者,孙武提出了许多关于合理运用人力、物力获取战争胜 利的见解,从运筹的正确与否关系到战争的胜败这样的高度来考察运筹 问题,突出了先谋后战、以谋为本的重要作用,体现了丰富的军事运筹 思想。 在西方,军事运筹学中的兰彻斯特战斗方程是于1914年提出的,丹 麦工程师爱尔朗于1917年提出排队论的一些著名公式,列温逊于1920年 研究了商业运筹学中的零售问题。运筹学作为一门学科是在第二次世界 大战期间,在研究武器的配置、兵力的部署和军需品的调运问题时产生 的。Operations Research这个名词最早出现于1938年,当时英国波得塞 雷达站的负责人A.P.洛维为了研究整个防空作战系统的合理运作,以便 有效地防备德国飞机入侵,成立了由各方面科学家组成的研究小组,并 以OR命名了这种研究活动。第二次世界大战期间,运筹学已经有了很 大的发展,战后其研究重点转向了民用部门,同样也获得了成功。1947 年,美国数学家G.B.Dantzig提出了求解线性规划的有效方法——单纯形 法,并于20世纪50年代初成功应用于电子计算机求解线性规划。到20世 纪50年代末期,发达国家已对企业中的一些普遍性问题,如库存、资源 分配、设备更新、任务分派等进行研究,并成功地将OR应用到建筑、 纺织、钢铁、煤炭、石油、电力、农业等行业。到20世纪60年代,运筹 学已经应用到服务性行业和社会公用事业。 2.内容分支 运筹学包括多个分支:规划论(包括线性规划、非线性规划、整数 规划和动态规划等)、库存论、决策分析、排队论、博弈论、图论、可 靠性理论等,这些分支构成了一个完整的运筹学理论体系。现在对部分 常见的分支作简要介绍。 1)规划论也称为数学规划,是运筹学的一个重要分支,主要包括 线性规划、非线性规划、整数规划、目标规划和动态规划。研究内容与 生产活动中有限资源的分配有关,在组织生产的经营管理活动中,规划 论具有极为重要的地位和作用。它主要解决两个方面的问题,一是对于 给定的人力、物力和财力,怎样才能发挥它们的最大效益;二是对于给 定的任务,怎样才能用最少的人力、物力和财力去完成它。这两个方面 有一个共同特点,即在给定的条件下,按照某一衡量指标来寻找最优方 案。具体来讲,线性规划可以解决生产过程的优化、物流运输以及资源 配置的优化问题等;整数线性规划可以求解企业的投资决策问题、旅行 售货员问题(Traveling Sales Problem)等;目标规划是线性规划的一种 特殊应用,主要处理在多种目标的情况下,选择合理方案的问题;而动 态规划所研究的对象是多阶段决策问题,主要用来解决最短路径问题、 多阶段资源分配问题、生产和库存控制及设备更新等问题。例如,某家 制造公司利用线性规划的科学理论对生产的成本和劳动力进行分配,使 得企业节约了10%的生产制造费用。此外,规划论还可以用于生产作业 计划、日程表的编排、配料问题等方面。 2)库存论,也称为存储论,它是研究物资库存策略的理论,主要 研究的是在生产和消费过程中,原材料、半成品或者商品的存储问题。 当存储少了的时候,会因停工待料或失去销售机会而遭受损失,而存储 多了又会造成资金积压、原材料及商品的损耗。因此,如何确定合理的 库存、补货量和补货周期至关重要,这便是库存论。合理的库存可以减 少资金占用、费用支出和不必要的周转,缩短物资流通周期,加速再生 产的过程等。常见的库存控制模型有确定性库存模型和随机性库存模 型。针对库存物资的不同特性,可选用相应的库存控制模型和补货策 略,制定出一个合理的库存系统。 3)所谓决策分析,就是根据客观可能性,借助一定的理论、方法 和工具,分析问题并提出可行方案,以及研究从多种可供选择的行动方 案中选择最优方案的方法。决策问题通常分为3种类型:确定型决策、 风险型决策和不确定型决策。经济领域中利用决策分析解决的问题有: 企业管理者指定投资、生产计划、物资调运计划、新产品销路、新股发 行的变化问题等。 4)排队论,也称为随机服务系统理论,是专门研究由于随机因素 的影响而产生拥挤现象的科学,主要用于解决系统服务设施和服务水平 之间的平衡问题,力争以较低的投入提供更好的服务。排队现象在日常 生活中屡见不鲜,如机器等待修理、船舶等待装卸、顾客等待服务,等 等。当然,该现象在经济领域中也很多见,如银行的信用卡申请系统在 核实信息、审查过程、等待工作人员操作等。这些问题有一个共同点, 即若等待时间过长,会影响生产任务的完成,或者影响经济效益,比如 顾客会选择自动离去而影响了业绩;若增加修理工、装卸码头和服务 台,能够解决等待时间过长的问题,但是又会带来修理工、码头和服务 台空闲的损失。排队论就是研究这类问题的理论。 5)博弈论,简单地说,是指两个或多个参与者在平等的对局中各 自根据对方的策略变换自己的对抗策略,以达到取胜目标的理论,也叫 做对策论。它是现代数学的一个新分支,也是运筹学的一个重要理论。 事实上,博弈论衍生于古老的游戏或博弈,如象棋、扑克等。数学家们 将具体的问题抽象化后,再建立完备的逻辑框架和体系来研究其规律和 变化。《史记》中记载的田忌赛马的故事就是一个典型的博弈论故事。 田忌用自己的上等马、中等马和下等马,分别对弈齐威王的中等马、下 等马和上等马,最后获得了比赛的胜利。从赛马的故事中可以看出,对 弈者要想在比赛中获胜,必须能准确地判断出己方的优势和劣势,从而 采取相应的战术。 3.应用领域 半个多世纪以来,运筹学一直保持着运用科学的方法解决重大实际 问题的特点,并因此获得了强大的生命力。它的每一分支都有明显的实 际背景,并且都和一定的经济形势相联系。运筹学在管理领域的应用至 少体现在以下几方面。 1)市场销售。主要应用在广告预算和媒介的选择、竞争性定价、 新产品开发、销售计划的制定等方面。 2)生产计划。主要用于确定生产、存储和劳动力的配置计划等方 面,以适应波动的需求;还可以用于生产作业计划、日程表的编制等环 节;也可以用于合理下料、配料和物料管理等方面。 3)库存管理。主要应用于设定多种物资库存量,以确定某些设备 的能力或容量,如停车场的大小、新增发电设备的容量大小、合理的水 库容量等。目前,国外的趋势是将库存理论与计算机物资管理系统相结 合,SAS公司也推出了用于库存管理的SAS/IRP产品,帮助企业对单层 级或者多层级的供应链进行管理,计算合理的目标库存水平、安全库存 和补货量等指标。 4)运输问题,这里涉及空运、水运、公路、铁路、管道和场内等 运输问题。空运问题涉及飞行航班和飞行机组人员服务时间安排等。为 此,在国际运筹学协会中专门设有航空组,以研究空运中的运筹学问 题。水运包括船舶航运计划、港口装卸设备的配置和船到港口后的运行 安排等。公路运输除了汽车调度计划外,还有公路网的设计和分析,市 内公共汽车线路的选择和行车时刻表的安排,出租汽车的调度和停车场 的设立等。当然,铁路运输方面的应用也很多。 5)人事管理,这里涉及6个方面,首先是人员的获得和需求估计; 第二是人才的开发,即进行教育和培训;第三是人员的分配,主要是各 种指派问题;第四是各类问题的合理利用;第五是人才的评价,其中有 如何测定一个人对企业和社会的贡献;第六是工资和津贴的确定等。 6)城市管理,这里包括各种紧急服务系统的设计和运用,如消防 站、救护车、警车等分布点的设立等。美国曾用排队论方法来确定纽约 市紧急电话站的值班人数,加拿大曾研究一城市的警车的配置和负责范 围,以及出现事故后警车应走的路线等。 7)定价管理,根据成本、现有库存、需求模式以及竞争者价格, 合理确定每天或者每一阶段产品的销售价格。 19.2 优化模型的基本概念 本书中将重点介绍在运筹学中占重要地位的线性规划和整数规划模 型,并学习如何运用SAS/OR对这类模型进行求解,这里的数学模型都 是规范模型(Prescriptive models),也称为优化模型(Optimization models)。一个优化模型包含以下3个部分: ·目标函数 ·决策变量 ·约束条件 简单地讲,优化模型的求解就是,寻找使得目标函数达到最优(最 大或者最小)的决策变量的取值,同时,让这些决策变量的值满足给定 的约束条件,这一求解过程称为数学优化(mathematical optimization),或者简称为优化(Optimization)。 例19.1:某工厂计划安排甲、乙两种产品的生产,它们的耗材(公 斤/单位)和利润(元/单位)状况如表19.1所示。该工厂要如何安排生 产才能使得利润最大? 表19.1 产品用料需求 假设该工厂生产x1个单位的产品甲,生产x2个单位的产品乙,则其 利润函数为: 利润=4x1+6x2 1.目标函数 在很多模型中,都有一个我们希望最大化或者最小化的函数,这个 函数就称为目标函数。在例19.1中,工厂希望合适安排生产,使得利润 能够达到最大,那么利润函数就是该优化问题的目标函数。 很多情况下,在解决企业的实际问题时,都会涉及多个目标函数。 如,在ATM取款机的现金补给优化问题中,客户希望在安排ATM取款 机现金补给的过程中能够考虑以下目标: ·最小化所有ATM取款机发生现金短缺的时间 ·最小化所有ATM取款机发生现金短缺的次数 ·最小化缺货量 ·最小化补给次数 ·最小化资金占用成本 在后面的章节中,将会介绍如何解决多目标规划问题。 2.决策变量 决策变量也称为控制变量或者操作变量。我们可以通过控制这些决 策变量来控制或者影响整个系统。在例19.1中,产品甲和产品乙的产量 x1和x2就是决策变量。 3.约束条件 在很多情况下,决策变量并不是可以任意取值的。在例19.1中,产 品甲或者产品乙的产量必须是整数个单位,不可以只生产1/2个单位或 者1/3个单位,并且产品的产量不可以是负值。由于材料A、B、C的供 给有限,因此产品产量也不可能任意大,并且若多生产了产品甲,可能 就要少生产产品乙。这种决策变量必须满足的条件就称为约束条件。例 19.1中的材料约束如下: ·材料A的资源量最多为18公斤。 ·材料B的资源量最多为10公斤。 ·材料C的资源量最多为16公斤。 用数学公式可以将约束表示为如下形式: 2x1+3x2≤18 2x1+x2≤10 x1+4x2≤16 x1≥0 x2≥0 x1,x2都为整数 令f(x1,x2)表示目标函数,也就是这里的利润函数,这个例子的 完整的优化模型则可以表示为: Maximize f(x1,x2)=4x1+6x2 s.t. 2x1+3x2≤18 2x1+x2≤10 x1+4x2≤16 x1≥0 x2≥0 x1,x2都为整数 其中,“s.t.”是Subject to的简写,表示“使得”。 概括起来讲,优化模型的一般形式可以表示为: Maximize|Minimize f(x) s.t. ci(x){≤,=,≥}bi(i=1,2,…,n) lj≤xj≤uj(j=1,2,…,n) 其中,x是决策变量,f(x)是目标函数,ci(x)和x的取值界限是 决策变量x的约束条件,bi是模型的参数。目标函数和约束条件可以线 性的或者非线性的,约束条件可以是有界约束(如,lj≤xj≤uj)、等式约 束(如,ci(x)=bi)、不等式约束(如,ci(x)≤bi)或者整数约束 (如,x1,x2都为整数)。 所有满足约束条件的决策变量的取值集合称为可行域(Feasible region),在可行域中,使得目标函数取得最优值的决策变量的取值称 为最优解(Optimal solution)。 19.3 优化模型的分类 1.静态模型和动态模型 根据系统是否随时间变化而变化,优化模型可以分为静态模型和动 态模型两种。静态模型是指决定系统特征的因素不随时间推移而变化的 模型。在现实世界中,不存在绝对的静态系统;静态系统的假定本身是 对系统的一种简化。若系统对象的主要特征在我们所关心的时间段不发 生明显变化,或者发生的变化对系统的整体性质没有明显影响,则把一 个系统看成是静态的是一种明智的选择。一般而言,静态模型相对比较 简单,建立静态模型的关键就是找到模型的平衡关系,并用数学公式将 其表示出来。例19.1中的模型就是一个静态模型,模型的最优解将告诉 工厂如何安排生产可以使得所有时间段的利润达到最大。 动态模型是指系统的状态随时间推移而变化的模型。动态模型根据 时间的可分性分为离散性动态模型和连续性动态模型两大类。离散性动 态模型是指模型中的时间变量采用离散的形式,连续性动态模型是指模 型中的时间变量采用连续的形式。 例如,一家生产运动服饰的公司根据需求安排未来一年的生产计划 就是一个动态问题,该公司必须决策未来一年里每个季度的产量,该决 策涉及多个阶段(多个季度),因此,解决该问题时需要建立动态模 型。 2.线性模型和非线性模型 根据优化模型中目标函数和约束条件的特征,可将优化模型分为线 性模型和非线性模型。在目标函数和约束条件中,如果决策变量都是线 性形式,则优化模型为线性模型;如果目标函数或者约束条件中含有决 策变量的非线性形式,则优化模型为非线性模型。例19.1中优化模型的 目标函数和约束条件都是决策变量x1和x2的加法形式,因此,该模型是 一个线性模型。在第20章和21章中,将重点讨论线性模型的建模和求 解。 3.整数模型和非整数模型 根据优化模型中决策变量的取值特征,可将优化模型分为整数模型 和非整数模型。如果模型中的一个决策变量或者多个决策变量的取值只 能是整数,则模型为整数模型;如果模型中的所有决策变量的取值都可 以是分数或者小数,则模型为非整数模型。例19.1中,产品甲和产品乙 的数量只能取整数,显然,该模型是一个整数模型。相比较而言,整数 模型的求解比非整数模型要复杂。 当模型中的部分决策变量必须取整数值,而其他的决策变量可以取 分数值或者小数值时,模型也称为混合整数模型。在第23章中,将介绍 混合整数模型的求解。 除了上面介绍的分类以外,优化模型还有更为详细的分类,如表 19.2所示。这些模型可以看成是线性模型、非线性模型和整数模型、非 整数模型的结合。 表19.2 优化模型分类 4.确定性模型和随机模型 决策变量的取值一旦确定下来,目标函数的取值和约束条件是否满 足也完全可以确定,则该模型为确定性模型;如果决策变量的取值确定 后,目标函数的取值或者约束条件是否满足仍然不能完全确定,则该模 型为随机模型。很明显,例19.1中的模型是一个确定性模型。至于前面 提到的生产运动服饰公司的例子,由于需求是未知的,所以对于给定的 生产计划,我们也不能确定是否能够满足需求,因此,该公司的生产计 划的安排适合使用随机模型。很多情况下,通过分析确定性模型可以为 随机模型的分析提供一定的参考。 19.4 优化建模步骤 运筹学作为一门用来解决实际问题的学科,在处理千差万别的实际 问题时,一般会涉及以下6个步骤:确定问题、准备数据、建立模型、 模型求解、模型验证与调试、模型实施与监控。 1.确定问题 在实际生活中,我们碰到的商业问题往往并不是这么明确和具体 的。因此,研究商业问题的第一步是确定问题,包含确定合理的目标、 探讨存在的约束、所需要研究的领域和组织机构内其他领域之间的关 系、分析决策可能产生的影响以及制定决策的时间周期要求,等等。问 题的确定是非常关键的一步,直接决定了整个OR研究的根基。 2.准备数据 在确定问题之后,我们要根据问题的需要进行数据收集。例如,表 19.1中产品甲和产品乙对于材料A、B、C的单位产量用料需求、单位产 量的利润和各种资源供给量等数据进行了收集和计算。 3.建立模型 在这一步中,要根据对问题和系统的理解,厘清目标函数、约束条 件和决策变量,并将它们用数学模型表示出来,比如,例19.1中建立的 数学模型。现在再举一个根据现实业务要求建立模型的示例。 例19.2:某汽车公司在4S店仓库容量、产品种类和预算的约束下, 希望通过优化确定所有4S店各种车型的订购量,从而使得整个公司的缺 货量达到最小。i表示4S店,j表示车型,k表示库存量。Di,j表示4S店i 的顾客对车型j的需求量,Si,j表示4S店i对车型j的供给量,于是有 模型中使用的参数如下: ·Spacei为4S店i的仓库容量约束。 ·Varietyi为4S店i可以订购的产品种类约束。 ·Budgeti为4S店i的订购预算约束。 ·Ci,j,k为4S店i中车型j的订购量为k时的单位订购成本。 ·Total_Budget表示整个公司的订购预算约束。 目标函数是 4.模型求解 在建立了数学模型之后,就要对模型进行求解,也就是寻求使得目 标函数达到最优的决策变量的取值。对模型求解的算法可以分为两大 类:一类是最优化算法,另一类是启发式算法。本书中将主要结合 SAS/OR软件介绍两类广泛使用的最优化算法:求解线性规划问题的单 纯形法,以及求解混合整数规划问题的分支定界法和割平面法。 对于有些问题,要找出问题的最优解,可能需要花费巨大的人力、 物力和财力,这时可以考虑使用启发式算法进行求解。启发式算法是相 对于最优化算法提出的,是一个基于直观或经验构造的算法,在可接受 的花费下给出待解决优化问题的一个可行解。在处理许多实际问题时, 启发式算法通常可以在合理时间内得到不错的解,但是该可行解与最优 解的偏离程度是不能事先预计的。 5.模型验证与调试 开发一个大型的优化模型,和开发一个大型的计算机应用程序类 似。在完成计算机程序的最初版本时,几乎不可避免地会出现多种错 误,需要经过多轮测试和不断修复。开发优化模型自然也是如此,最初 的模型同样不可避免地会包含一些缺点,比如说,一些相关的因素没有 被考虑进去,或者,模型中一些参数的估计不合适等。在建模过程中, 由于系统的复杂性特点,使得了解系统的方方面面具有一定的难度,同 时,数据收集也具有一定难度,这些都不可避免地导致初期的优化模型 存在多种问题。因此,在优化模型投入使用之前,必须进行多次验证和 调试。 很难具体描述如何进行模型的验证与调试,因为这都是因优化问题 和模型而异的,这里给出进行模型验证与调试的几条一般性的建议: ·对于大型的优化项目,很多情况下,都会将其分成一个一个小的 组成部分分别进行处理,但这样一来,在开发的过程中很容易犯因小失 大的错误。故在进行模型的验证与调试的时候,要从整体着手,将各个 部分联系起来。参与这项工作的人员最好未曾参与模型的建立,这样可 以公平客观地对待整个问题。 ·在进行模型验证的时候,应重新检验优化问题的定义,并与开发 的模型进行比较。 ·通过输入不同的参数,检验模型的结果是否合理。例如,在例19.2 中,调整模型的参数Spacei或Varietyi的取值,检验模型的结果。 ·采用回顾性检验方法,选取一段历史时间作为验证阶段,利用这 段时间之前的数据建立模型,根据建立的模型模拟这段时间的数据,并 生成KPI指标,然后与实际发生的数据和KPI指标进行比较。通过对 比,可以对模型的效果有初步的了解,并且可以揭示模型的不足之处, 便于修正和调试。 6.模型实施与监控 在模型的验证与调试通过之后,就可以进行模型实施了,这时要将 模型与实际生产环境集成起来。在模型的实施过程中,必须不断监测模 型运行的结果,检查是否能够满足建立模型时提出的目标。 19.5 SAS/OR简介 如前所述,基本的优化问题就是在一系列的约束下,最优化(最大 化或者最小化)一个目标函数,如: Maximize|Minimize f(x) s.t. ci(x){≤,=,≥}bi(i=1,2,…,n) lj≤xj≤uj(j=1,2,…,n) 在建立数学模型后,对数学模型进行求解就是寻找合适的决策变量 的取值使得所有的约束条件都能满足,并且目标函数取得最优值(最大 值或者最小值)。对此,SAS/OR提供了一系列求解优化模型的过程 步。 ·OPTLP过程:用于求解线性规划模型,其中目标函数是线性函 数,约束条件可以是有界约束、等式约束或不等式约束,但是必须都是 线性形式。 ·OPTMILP过程:用于求解混合整数线性规划模型,其中目标函数 是线性函数,约束条件可以是有界约束、等式约束或不等式约束,但是 必须都是线性形式,并且部分或者全部决策变量受制于整数约束。 ·OPTQP过程:用于求解一类特殊的非线性规划模型,也称为二次 规划模型(Quadratic Programming)。在该种非线性规划模型中,目标 函数是二次型函数,约束条件可以是有界约束、等式约束或不等式约 束,但是必须都是线性形式。 在调用以上3个过程步求解优化模型前,需将用于优化模型的参数 保存成要求的SAS数据集,然后调用相应的过程步进行求解。本书中将 不会具体介绍这3个过程步的使用方法,有兴趣的读者可以参考SAS帮 助文档学习其使用方法。 除了上面3个过程步,还有一个OPTMODEL过程步,作为一种代数 模型语言,OPTMODEL过程使得SAS/OR用户在运用SAS建立优化模型 的过程中,可以根据数学模型的表达形式使用代数语言编写程序,极大 地提高了优化模型的可读性和透明性。运用OPTMODEL过程可以调用 SAS/OR软件的LP、MILP、QP和NLP求解器,求解线性规划模型、混 合整数线性规划模型、二次规划模型和一般的非线性模型。本书将对其 使用方法进行重点介绍。 19.6 一个简单的OPTMODEL程序 在例19.1中,建立了工厂生产甲、乙两种产品的优化模型,这里将 展示如何用OPTMODEL过程将该优化模型表示出来,并进行求解。 ·目标函数是利润函数,优化的目标是最大化利润函数,即 Maximize 4(product1)+6(product2) ·产品甲和产品乙关于材料A的总耗材不能超过材料A的总供给量, 因此 2(product1)+3(product2)≤18 ·产品甲和产品乙关于材料B的总耗材不能超过材料B的总供给量, 因此 2(product1)+1(product2)≤10 ·产品甲和产品乙关于材料C的总耗材不能超过材料C的总供给量, 因此 1(product1)+4(product2)≤16 ·产品甲和产品乙的产量product1和product2都必须是整数。 用OPTMODEL过程建立模型的代码如下: proc optmodel; /*declare variables*/ var Product1 >=0 integer, Product2>=0 integer; /*maximize objective funtion (profit)*/ maximize profit = 4*Product1+6*Product2; /*subject to constraitns*/ Con materialA: 2*Product1+3*Product2 <=18; Con materialB: 2*Product1+1*Product2 <=10; Con materialC: 1*Product1+4*Product2 <=16; /*solve MILP*/ Solve with MILP; /*display solution*/ print Product1 Product2; quit; 这段代码几乎和数学模型的表达方式一模一样,非常简洁易读。 VAR语句声明了决策变量,MAXIMIZE语句声明了目标函数,CON语 句声明了3种材料的资源约束。在模型建立完毕后,运用SOLVE语句调 用MILP求解器求解该优化模型,最后通过PRINT语句将决策变量的取 值输出。 这段代码的输出如图19.1和图19.2所示。 OPTMODEL过程输出的第一张报表是问题的总结,包含优化问题 的类型、目标函数、目标函数的类型、决策变量的信息以及约束的信 息。第二张报表输出了运行OPTMODEL过程的系统信息。第三张报表 是模型求解过程的总结,包含求解使用的求解器信息、算法、优化状态 和优化后的目标函数取值、迭代次数等信息。最后一张是PRINT语句输 出的模型最优解。可以看到,产品甲和产品乙分别生产3个单位能使利 润达到最大。 图19.1 OPTMODEL过程输出内容1 图19.2 OPTMODEL过程输出内容2 不过,在上面这段代码中,模型的参数和模型结构是混合在一起 的,如果某些模型参数发生变化,则需要到代码中去调整模型。对于大 型优化模型来说,这种操作方法非常不便,也不利于模型的实施。在使 用OPTMODEL过程的时候,可以将数据和模型结构分离出来。因此, 上段代码可以修改成以下形式: data work.product; length Name $10.; input Name $ profit; datalines; Product1 4 Product2 6 ; run; data work.required; length Name $10.; input Name $ A B C; datalines; Product1 2 2 1 Product2 3 1 4 ; run; data work.material; length material $1.; input material $ available; datalines; A 18 B 10 C 16 ; run; proc optmodel; /*declare sets and data indexed by sets */ set<string> PRODUCT; set<string> MATERIAL; num profit{PRODUCT}; num required{PRODUCT,MATERIAL}; num available{MATERIAL}; /*declare variables*/ var Units{PRODUCT} >=0 integer; /*maximize objective function (profit)*/ Maximize totalprofit=sum{p in PRODUCT}profit[p]*Units[p]; /*subject to constraitns*/ Con availablity{m in MATERIAL}: sum{p in PRODUCT} required[p,m]*Units[p]<=available[m]; /*abstract algebaric model that captures the structure of the optimization problem has been defined without referring to a single data constant */ /*populate model by reading in the specific data instance*/ Read data work.product into PRODUCT=[name] profit; Read data work.material into MATERIAL=[material] available; Read data work.required into PRODUCT=[name] {m in MATERIAL} <required[name,m]=col(m)>; /*solve MILP*/ Solve with MILP; /*display solution*/ print Units; quit; 上述代码中,首先将模型中的参数分别保存在了3个不同的数据集 work.product、work.required和work.material中。OPTMODEL程序中的上 半段中定义了模型结构,将数据和模型结构完全分离了,后半段中运用 READ DATA语句将数据集中的参数加载进模型,然后进行求解。这样 建立的模型更加灵活、可读,运行上述代码后,输出结果和图19.1和图 19.2完全一样。这段代码主要展示了OPTMODEL过程可以将数据和模型 完全分离的功能,所有关于OPTMODEL过程的语法将在第20章、21章 和22章中相继介绍。 19.7 本章小结 这一章是本书优化部分的开篇。首先简要介绍了运筹学的发展历 史、内容分支和应用领域。接下来介绍了优化模型的基本概念,如目标 函数、约束条件和决策变量,并对优化模型作出不同的分类。然后,介 绍了优化建模从确定问题、准备数据到模型实施与监控的6个基本步 骤。在介绍这些基本概念之后,通过一个示例简单扼要地介绍了如何运 用SAS/OR进行优化建模和求解。 第20章 线性规划 线性规划(Linear Programming)是运筹学中经典且又相对比较成 熟的分支。1947年美国数学家G.B.Dantzig对如何求解线性规划问题提出 了单纯形法(Simplex Method),由于单纯形法可以求解大规模线性规 划问题并且易于在计算机上实现,因此它的出现极大地促进了线性规划 的广泛应用。此外,经过多年的研究,贝尔实验室的N.K.Karmarkar于 1984年对线性规划问题提出了全新的内点迭代求解模式。该算法为研究 更大型的线性规划问题的数值解法提供了有效的途径。本章在介绍线性 规划问题基本理论的基础上,将结合SAS/OR介绍如何运用单纯形法、 对偶单纯形法和内点法求解线性规划问题。 20.1 数学模型 这一节中,将介绍线性规划问题及用于描述线性规划问题的一些重 要术语。 20.1.1 问题的提出 例20.1:一家生产车的制造商生产甲、乙两种类型的卡车,每辆卡 车都必须经过喷漆和组装两个步骤。喷漆间和组装间每天都是8小时工 作制,如果仅为甲种卡车喷漆,那么喷漆间每天可以完成800辆;如果 仅为乙种卡车喷漆,那么喷漆间每天可以完成600辆。如果组装间仅组 装甲种卡车,那么组装间每天可以组装700辆;如果仅组装乙种卡车, 每天可以组装800辆。生产一辆甲种卡车的利润为400元,生产一辆乙种 卡车的利润为500元。应如何安排每天的生产,使得该制造商的利润达 到最大? 在第19章中,已对于一般的优化问题引入了决策变量、约束条件和 目标函数等术语。那么,现在来看看这个问题中的决策变量、约束条件 和目标函数分别是什么。在线性规划模型中,决策变量必须代表决策者 作出的决策。这里的决策者是制造商,该制造商必须决定每天生产多少 辆甲种卡车和多少辆乙种卡车。显然,这里的决策变量就应该是每天生 产甲种卡车和乙种卡车的辆数,分别记为x1和x2。 在任何线性规划问题中,决策者都必须最大化(如收入、利润)或 者最小化(如成本)由决策变量组成的目标函数。在这个问题中,每生 产一辆甲种卡车或乙种卡车的利润分别为400元和500元,这里的目标函 数即为400×x1+500×x2。该制造商需要选择合适的x1和x2使得 400×x1+500×x2达到最大。在线性规划中,可以使用z表示目标函数,则 该问题的优化目标和目标函数为: Maximize z=400×x1+500×x2 这里Maximize可以简写成max。如果是最小化目标函数,则应写成 Minimize,简写为min。目标函数中,决策变量的系数称为目标函数系 数(objective function coefficient),这里的400和500都是目标函数系 数,代表了每生产一辆甲种或乙种卡车对利润的贡献。 这里是最大化目标函数,如果对每天生产甲种或乙种卡车的数量没 有约束的话,那么为了追求利润的最大化,该制造商一定会生产尽可能 多的卡车。但是,由于每天喷漆间和组装间的工作时间有限,每天的生 产不可能是无止境的。这里的约束条件有两个,一是每天使用喷漆间的 时间不能超过8小时,二是每天使用组装间的时间不能超过8小时。接下 来的问题就是,如何运用决策变量x1和x2来表示约束条件。这是在建立 线性规划模型时要考虑的比较复杂的一个步骤,特别是对于大型的线性 规划问题而言。对于约束条件一,我们注意到,每辆甲种卡车的喷漆时 间为 小时,每辆乙种卡车的喷漆时间为 小时,则每天喷漆间生产 甲种卡车所用的时间为 小时,每天喷漆间生产乙种卡车所用的时间为 小时。因此,一天中喷漆间使用时间的约束为: 同样道理,一天中组装间使用时间的约束为 注意,约束条件中每一项的单位必须都是一致的,不等号左边和右 边的单位也必须是一致的,否则约束条件就失去实际意义。这里约束条 件的左边和右边都是表示小时数。 在约束条件中,不等号右边的数称为右端项(right-hand side, rhs),通常,约束条件的rhs代表了资源的最大供给量,这里表示工作 时间的最大量。约束条件中决策变量的系数称为约束条件系数。 在建立线性规划模型时,一个自然而然的问题是,决策变量是否只 可以取非负值,还是说决策变量的取值可正可负?如果一个决策变量xi 只可以取非负值,则在建立线性规划模型的时候必须添加符号约束 xi≥0;如果一个决策变量xi的取值可正可负甚至可以是0,则称这个决策 变量xi是自由的(unrestricted in sign,urs)。在这个生产卡车的问题 中,显然x1≥0,x2≥0。在其他问题中,有些决策变量是自由的。例如, 如果决策变量xi代表某公司的现金结余,如果该公司欠的钱比实际拥有 的钱多,那么xi的取值就是负值。在这种情况下,xi就是自由的。实际 上,符号约束是约束条件中的一种。 结合决策变量、目标函数、约束条件及符号约束考量,针对该卡车 生产的优化模型如下: Maximize z=400×x1+500×x2 x1≥0 (符号约束) x2≥0 (符号约束) 20.1.2 线性规划问题 1.线性规划问题的定义 在定义线性规划问题之前,首先给出线性函数和线性不等式的定 义。 ·线性函数:如果一个关于x1,x2,…,xn的函数f(x1,x2,…, xn)是线性函数,那么f(x1,x2,…,xn)=c1x1+c2x2+…+cnxn,其中 c1,c2,…,cn为常数。例如,f(x1,x2)=2x1+3x2为x1和x2的线性函 数,但是f(x1,x2)=2x1x22则不是x1和x2的线性函数。 ·线性不等式:对于任意的线性函数f(x1,x2,…,xn)和任意常 数b,不等式f(x1,x2,…,xn)≤b和不等式f(x1,x2,…,xn)≥b称 为线性不等式。例如,2x1+3x2≥3和2x1+3x2≤10为线性不等式,而 2x1x22≤7不是线性不等式。 我们将具有以下特征的优化问题称为线性规划问题(简称为LP问 题): ·目标函数是线性函数。 ·决策变量必须满足一系列约束,并且每个约束条件都必须是线性 等式或者线性不等式。 ·任意决策变量必须是非负的(如xi≥0)或者自由的。 因为例20.1中的目标函数是决策变量x1和x2的线性函数,所以所有 的工时约束都是线性不等式,并且x1和x2的取值为非负值,因此该优化 问题是线性规划问题。该生产制造问题是线性规划问题中最典型的生产 计划问题。 注意 在线性规划问题中,不允许出现“<”或“>”形式的约束。 根据线性规划问题的定义,不难推导出线性规划问题具有如下隐藏 的假设条件。 ·比例性(Proportionality):每个决策变量对目标函数的贡献都是 随着决策变量取值的增加而按固定比例变化的,不产生规模经济效应。 在约束条件中,每个决策变量对于约束条件左端项的贡献也是随着决策 变量取值的增加按固定比例变化的。 ·可加性(Additivity):在目标函数和约束条件中,决策变量之间 是相互独立的,没有协同项或交叉项,目标函数或者约束条件仅仅是各 个决策变量各自单独贡献的总和。例如,在例20.1中,不管同时生产多 少辆甲种卡车和乙种卡车,它们对目标函数的贡献始终是各自贡献的总 和400×x1+500×x,不会因为可以共用销售或市场渠道而获得额外利润, 也不会因为两种车型在市场定位上的重叠竞争而造成总利润减少。同 样,无论同时生产多少辆甲种卡车和乙种卡车,各自所花费的喷漆工时 和组装工时也不会改变,不会因为需要同时生产两个不同车型而要调整 喷漆或组装车间的人员或设备,从而花费更多的时间。 ·可分割性(Divisibility):决策变量的取值可以是分数或小数,并 非严格要求为整数。在例20.1中,可分割性的假设暗示了可以生产0.5辆 甲种卡车或者1.3辆乙种卡车。但在生产卡车的实际过程中,显然是不 可以生产非整数辆卡车的,因此可分割性这个假设在例20.1中不成立。 这种部分决策变量或全部决策变量只能取整数的线性规划问题称为混合 整数线性规划问题或整数线性规划问题。在很多的实际问题中,可分割 性的假设条件通常都不成立,但是通过对线性规划问题的最优解取整, 在某些情况下可以得到对应混合整数线性规划问题的较好解。例如,某 生产计划LP问题的最优解是每年必须生产15000.4辆轿车,那么我们基 本可以认为生产15000辆轿车或者生产15001辆轿车可能是最优的生产方 案。又比如,在某通信公司在某地区建设发射塔的线性规划问题中,求 解得到建立发射塔的最优数目为1.4台,这时如果进行取整,可得到1台 或者2台,但这对效益或成本的影响将非常大,这时必须使用混合整数 线性规划方法对这类问题进行求解了。 ·确定性(Certainty):优化模型中的参数(目标函数系数、约束条 件系数和右端项)必须都是已知常数。在例20.1中,如果不能确定每生 产一辆甲种卡车的利润是多少,那么确定性假设条件就不成立。 2.凸集和顶点 为了介绍线性规划问题解的概念,先来介绍一下凸集的概念。对于 集合中的任意两点而言,若这两点的连线也属于这个集合,则称该集合 为凸集(Convex Set)。如图20.1所示,图20.1a、b是凸集的两个例子, 图20.1c、d则明显不是凸集。 图20.1 凸集和非凸集示例 若有任意的x1和x2属于某个凸集S,0≤λ≤1,令x=λx1+(1-λ)x2,称 x为x1和x2的凸组合。对于任意凸集中的某个点,如果该点不能表示为 该凸集中任何两个不同点的凸组合,则该点为凸集的顶点(Extreme Point)。在图20.1a中,圆周上的任何点都是该凸集的顶点,图20.1b中 点A、B、C、D、F都是凸集的顶点,E点不是凸集的顶点,因为E可以 表示成C点和D点的凸组合。 3.解的概念 在线性规划问题中,决策变量的任何一个取值都被称为一个解,但 是一个解只有在满足所有约束条件时才被称为该问题的可行解,所有可 行解构成的集合称为该问题的可行域,使目标函数值达到最优的可行解 称为该问题的最优解。对于任意的解,如果该解不在该问题的可行域 中,则称为不可行解。 例20.1中LP问题的可行域就是满足工时要求的所有生产计划的集 合。大部分线性规划问题都有唯一的最优解,有一些线性规划问题包含 无穷多个最优解。在本节后面将会介绍相应的情形。 可以证明,如果一个线性规划问题的可行域存在并且是有界的,那 么该可行域是凸集,并且其最优解必然可以在可行域的顶点处达到。这 个结论非常重要,因为这个结论将使线性规划问题最优解的搜索范围从 一个无限的集合缩小到一个只包含顶点的有限的集合。 20.1.3 图解法 对于仅含两个变量的线性规划问题,可以通过图解的方法求得其最 优解。下面的例子给出了运用图解法求解线性规划问题的基本步骤。 例20.2:求解以下线性规划问题: Maximize z=12x+19y s.t.x+3y≤225 x+y≤117 3x+4y≤420 x≥0,y≥0 步骤1:在直角坐标系中画出线性规划问题的可行域。 步骤2:作目标函数的等值线。本例中,由z=12x+19y得, 对 于不同的z值,则上述方程给出了斜率同为 的一族平行线,这无数条 平行直线构成了该线性规划问题的等值线族。 步骤3:确定最优解。等值线族覆盖于可行域上,显然,可行域上 与最大等值线相交的点即为该线性规划问题的最优解。所以最优解 (x*,y*)满足 解得(x*,y*)为(63,54),目标函数的最优值为1782。 该图解法如图20.2所示,最优解在可行域的顶点处取到。 图20.2 例20.2的图解法 一旦找到线性规划问题的最优解,就可以将最优解回代到约束条件 中,从而将约束条件分为紧约束(binding constraint或者active constraint)和非紧约束(nonbinding constraint)。 ·最优解代入到某约束条件中后,如果约束条件左边的取值等于右 边项,则该约束条件称为紧约束。 ·最优解代入到某约束条件中,如果约束条件左边的取值和右边项 不相等,则该约束条件称为非紧约束。 在例20.2中,x*+y*=117,故约束条件x+y=117为紧约束,而 3x*+4y*<420,所以约束条件3x+4y≤420为非紧约束。 运用图解法求解例20.2的过程直观地表明,对于仅含有两个决策变 量的线性规划问题而言,其最优解存在4种可能,且仅存在这4种可能: ·有唯一最优解。 ·有无穷多个最优解。 ·有可行解而无最优解,此时线性规划问题也称为Unbounded LP。 ·无可行解,此时该线性规划问题也称为Infeasible LP。 例20.2对应了第一种情形,即有唯一的最优解的情形。图20.3依次 给出了另外3种情形,其中的阴影部分表示可行域。 来看看图20.3中的情形,在有无穷多个最优解的示例中,约束条件 x+3y≤225的斜率和等值线族的斜率一样,当x+3y=225时,取得最优 解,并且目标函数的最优值为225,但是此时,x和y的取值有无数种可 能组合。在有可行解而无最优解的示例中,作出了可行域,但是该可行 域是上无界的,y可以取任意大的值,因此目标函数也不存在最大值, 因此不存在最优解。在无可行解的示例中,由于约束条件3x+4y≥420和 其余约束条件相冲突,造成了可行域为空集合,故不存在可行解。 图20.3 最优解其余3种情形 可以证明,对于所有的线性规划问题,其最优解也仅存在这4种可 能。 20.2 单纯形法 在实际问题中,LP问题通常有多于两个的决策变量,因此需要寻找 一个方法来求解含有两个以上决策变量的LP问题,单纯形法就是这样一 个基本而且有效的求解方法。在解决实际的商业问题时,单纯形法被用 于求解包含成千上万个约束条件和决策变量的LP问题。该方法最重要的 一个原理就是前面所说的,如果一个线性规划问题的可行域存在并且是 有界的,那么该可行域是凸集,并且其最优解可以在可行域的顶点处达 到。 本节中将介绍如何运用单纯形法求解LP问题的最优解。 20.2.1 线性规划问题的标准型 前面介绍的线性规划模型中,其不等式可以是“大于等于”,也可以 是“小于等于”。为了便于对单纯形法进行讨论,下面引入线性规划模型 的标准型(Standard Form)。单纯形法是基于线性规划标准型的一种寻 找最优解的算法。 假设一个线性规划问题有n个决策变量x1,x2,…,xn,则其标准型 为: minimize z=c1x1+c2x2+…+cnxn s.t.a11x1+a12x2+…+a1nxn=b1 a21x1+a22x2+…+a2nxn=b2 … am1x1+am2x2+…+amnxn=bm xi≥0(i=1,2,…,n) 其中ci(i=1,2,…,n)为目标函数系数,aij(i=1,2,…,m, j=1,2,…,n)为约束条件系数,bj(j=1,2,…,m)为右端项。 若记 那么线性规划的标准型为: minimize z=cTx s.t. Ax=b x≥0 其中,A称为约束系数矩阵,c称为目标系数向量,b称为约束右端 项向量,cT为向量c的转置。 本书中介绍的LP问题的标准型具有以下3个特点: ·目标函数求极小。 ·约束条件为等式。 ·决策变量及约束右端项为非负。 在其他书中,也有以目标函数求极大的形式来定义LP的标准型的, 不过,这两种定义本质上没有区别。 怎么将一般的线性规划问题化为标准型呢?以下为进行转化的几种 途径: ·对于目标函数求极大的情况(maximizecTx),可以将目标函数改 写成(minimize-cTx)。 ·对于“小于等于”的不等式约束,如ai1x1+ai2x2+…+ainxn≤bi,应引入 松弛变量(slack variable)si≥0,将其化为等式 ai1x1+ai2x2+…+ainxn+si=bi 显然,新的约束条件 ai1x1+ai2x2+…+ainxn+si=bi si≥0 等同于原始的不等式约束 ai1x1+ai2x2+…+ainxn≤bi ·同理,对于“大于等于”的不等式约束,如ai1x1+ai2x2+…+ainxn≥bi, 应引入剩余变量(surplus variable)si≥0,将其化为等式 ai1x1+ai2x2+…+ainxn-si=bi ·对于右端项为负值的等式或不等式约束,在等式或不等式约束的 两边同时乘以-1。 ·对于负变量xj≤0,可令一个新的变量xj=-xj,再回代入模型中。 ·对于自由变量xj,可令其等于两个非负变量之差,x+j≥0,x-j≥0, xj=x+j-x-j,再回代入模型中。 以上几种操作途径的示例如图20.4所示。 图20.4 一般LP问题转化成标准型方法示例 为了便于对单纯形算法进行讨论,还需介绍几个解的概念。 考虑线性规划问题的标准型,A为m×n的系数约束矩阵,假设矩阵 A的前m列线性无关,则A可以分块表示为A=(BN),B为前m列形成 的矩阵,N为后n-m列形成的矩阵。矩阵B称为基矩阵。相应的,x也可 分块记为 由于有约束条件Ax=b,则 xB=B-1b-B-1NxN 令xN=0,则xB=B-1b,且有 。 ·基变量(basic variables)和非基变量(non basic variables):与基 矩阵B对应的变量称为基变量,其余的变量称为非基变量。 ·基解(basic solution): ·最优基:如果基解 是对应于基矩阵B的一个基解。 是最优解,那么B称为最优基。 ·基可行解(basic feasible solution):由于基解是由约束条件Ax=b 推出的,没有考虑非负约束,因此基解也不一定是可行解。满足非负约 束条件的基解,称为基可行解。 可以证明,基可行解和可行域的顶点是一一对应的。 20.2.2 单纯形法的导出和运用 前面介绍过如果一个线性规划问题存在最优解,则其最优解必然可 在可行域的顶点处达到,也就是必然在基可行解处达到。单纯形法是一 种迭代算法,其基本原理就是从线性规划问题的一个基可行解出发,通 过不断变化基变量,寻找到使得目标函数取得最优解的基可行解,也可 以理解为从单纯形法的一个顶点走向另一个顶点,直到在某个顶点上目 标函数取得最优值为止。 1.单纯形法的导出 考虑线性规划标准型LP(注意,为了方便表示,这里将所有的松弛 变量和剩余变量si都用某个xi来表示): minimize z=c1x1+c2x2+…+cnxn s.t.a11x1+a12x2+…+a1nxn=b1 a21x1+a22x2+…+a2nxn=b2 … am1x1+am2x2+…+amnxn=bm xi≥0(i=1,2,…,n) 假设约束矩阵A的秩为m,A=[a(1),a(2),…,a(n)],a(i) =(a1i,a2i,…,ami)T,b=(b1,b2,…,bm)T,x(0)=(x(0)1, x(0)2,…,x(0)m,0,…,0)T是LP问题的一个基可行解。那么可 以假设矩阵A的前m列线性无关,则必存在一组不全为0的数aij,i=1, …,m使得 由于x(0)是基可行解,且Ax(0)=b,可知 乘以λ并与等式 将等式 两边同 相加,可以推导出 由此可知,方程Ax=b具有以下形式的解: 那么,要使得x(1)成为LP问题不同于x(0)的新的可行解,则必须 有x(0)i-λaij≥0,且λ≥0;进一步,要使得x(1)成为LP问题的新的基可 行解,则必须至少有一个x(0)i-λaij=0(1≤i≤m)且λ>0,所以令λ的取值 为: 该式也称为出基准则。由此可见,出基准则保持了解的基可行性。 这样,x(1)是一个新的基可行解,并且前m个分量中会有一个分量为 0(假设是第r个分量),x(1)可以表示为: 将x(1)代入LP问题的目标函数,有 如果基可行解x(1)优于x(0),则必须有 也就是 令 ciaij,为了使目标函数的下降达到最大,则应选择j0使其满足 称为进基准则。由此可见,进基准则是确定迭 代的方向,可使迭代能够尽快地找到最优解。 对于m+1≤j≤n,如果所有的cj≥0,则说明LP问题的目标函数值已经 无法改进,即最优解已经达到,通常称cj称为判别系数或者价值系数 (Reduced Cost)。 根据进基准则和出基准则,可以判断: ·当所有的cj>0,m+1≤j≤n,则LP问题具有唯一的最优解。 ·一般来说,若存在cj=0,m+1≤j≤n,则LP问题可能具有无穷多个最 优解,但是,并不能表示LP问题一定有无穷多个最优解。 ·对于选定的进基列j0,若所有的aij0≤0,则LP问题有可行解但无最 优解。当确定进基列j0之后,由于对于所有的1≤i≤m,都有aij0≤0,那么 当λ取任意大于0的值时,x(1)的各个分量x(0)i-λaij0始终保持大于等于 0(1≤i≤m),所以x(1)是LP问题的可行解。又由 函数 可知,目标 的取值在λ趋向于正无穷时趋向于负无穷,故不存在最优 解。 2.单纯形法的一般步骤 基于以上原理,下面介绍运用单纯形法求解线性规划问题的一般步 骤: 1)将线性规划问题转化成标准型。 2)计算标准型的初始基可行解。单纯形表(Simplex Tableaux)是 单纯形法的一种常用的表示形式。对增广矩阵(A|b)做初等行变换: 因此,x(0)=(b1,b2,…,bm,0,…,0)T就是该线性规划问 题的一个初始的基可行解,列出得到初始单纯形表之前的一个中间表 格,如表20.1所示。 表20.1 初始单纯形表(中间表) 现在,继续对该表的矩阵进行初等行变换,将基变量x1,…,xm下 方对应的c1,…,cm转换成0,则得到LP问题的初始单纯形表,如表 20.2所示。 表20.2 初始单纯形表 这里,最后一行系数cj即为判别系数。在进初等行变换的过程中, 表格中的右下角的数字也将由0变成 即-z。 若记B为基矩阵,N为非基矩阵,cB为基变量对应的目标系数向 量,a(j)为非基矩阵N中的列,j∈N,对矩阵 以得到 进行初等行变换,可 即非基变量的判别系数cj的计算可以进一步表示为 表20.1中,B为单位矩阵,a(j)=(a1j,a2j,…,amj)T,cB=(c1,c2, …,cm)T,所以 3)检查非基变量的判别系数cj,对于目标函数极小化的情况下,若 所有的cm+1,…,cn都大于等于0,由前面单纯形法的导出原理可知,最 优解已经求得,计算可以终止(同理,对于目标函数极大化的情况下, 若所有cm+1,…,cn都小于等于0,则最优解已经求得,计算可以终 止),最优解为(b1,…,bm,0,…,0)T。否则,继续迭代。 4)如果从步骤3的判断中,得知仍需继续迭代,说明当前的基可行 解不是最优解。因此需要根据进基准则从当前的非基变量中选取某一个 变量作为进基变量,同时根据出基准则从当前的基变量中选取某一个变 量作为出基变量。选取j0,使得 假设这里j0=k,则xk为进基 变量。若所有的a1k,…,amk都小于等于0,则说明该线性规划问题有可 行解但无最优解;否则,选取i0,使得 假设这里i0=r,则xr为 出基变量。如表20.3所示。 表20.3 单纯形表——进基变量和出基变量示例 5)依据进基变量和出基变量的位置,对单纯形表进行初等行变 换,将基变量下方对应的判别系数转换成0,求得新一轮基可行解对应 的单纯形表。转至步骤3。 注意 在运用单纯形法求解线性规划问题时,右端项(-z除外) 必须是非负数;如果右端项出现负数,则表明此时的解不是基可行解。 在单纯形法的计算步骤4中,我们已经知道什么时候LP问题具有无 界解了。若通过上面的迭代,得到的单纯形表终表中,每个非基变量的 判别系数都是非0的,则LP问题具有唯一的最优解。但是,反之,即使 单纯形表终表中,某个非基变量的判别系数为0,也不能表示LP问题一 定有无穷多个最优解。 在单纯形法的迭代过程中,确定出基变量和进基变量时,可能出现 两个相同的最小判别系数或者两个相同的最小比值。在这种情况下,通 常运用Bland法则: ·选取下标最小的j0,使得 ·选取下标最小的i0,使得 20.2.3 以xj0作为进基变量。 以xi0作为出基变量。 两阶段单纯形法 在运用单纯形法求解LP问题时,将LP问题转换成标准型后,就需 要寻找初始基可行解了。 不失一般性,考虑LP问题的标准型: min z=cTx s.t.Ax=b x≥0 其中,A为m×n的矩阵,假设A的秩为m,c 如果原LP问题的所有约束都是“小于等于”的不等式约束,且右端项 都是非负数,则进行标准型转化,添加的松弛变量后,(0,…,0, b1,…,bm)T就是标准型的初始基可行解。 如果原LP问题的约束不都是“小于等于”的不等式约束,或者右端项 不都是非负数,则需像步骤2中介绍的方法一样,对标准型的增广矩阵 (A|b)使用初等行变换,从而计算初始基可行基。我们知道,进行初 等变换往往要花费相当大的计算量。为了方便地得到一个线性规划问题 的初始基可行解,通常可采用人工变量法,有两条实现途径:一种是大 M法(The Big M Method),一种是两阶段单纯形法(Two-Phase Simplex Method)。 使用大M法进行计算机编程时,有时会引起舍入偏差或一些其他计 算困难。因此,商业软件大多使用两阶段单纯形法,SAS/OR就是使用 的这种方法。故而这里也只介绍两阶段法的基本原理,有兴趣的读者可 以自行查阅大M法的相关内容。 两阶段单纯形法主要针对不能从标准型中找到明显的初始基可行解 的情况,它可通过引入人工变量 的方法,将LP问题分 成两个阶段进行求解。 ·Phase 1:求出原LP问题的一个初始基可行解。 ·Phase 2:运用单纯形法从初始基可行解出发,求得原LP问题的最 优解。 在Phase 1中,考虑如下问题: 显然,(0,…,0,b1,…,bm)T是该问题的一组基可行解。由 于w下有界,因此一定存在最优解。运用单纯形法寻找该问题的最优 解,记最优解为 ·若w*=0,即 则 是原LP问题的一个初始基可行解。 进入Phase 2,运用单纯形法对原LP问题进行求解。 ·若w*>0,则表明原LP问题所引入的人工变量中有非0值,考虑到 可知原LP问题没有可行解。 在SAS/OR中,不管是单纯形法还是接下来将介绍的对偶单纯形 法,都是运用两阶段法的思想进行求解的。使用单纯形法对LP问题求解 的语法如下: proc optmodel; /*declare variables*/ /*declare constraints*/ /*declare objectives*/ solve with lp/ solver=ps; quit; 例20.3:求解下列线性规划问题。 min z=4x1+x2 s.t.3x1+x2=3 4x1+3x2≥6 x1+2x2≤4 xj≥0,j=1,2 引入剩余变量s1和松弛变量s2,将原问题化为标准型,如下: min z=4x1+x2 s.t.3x1+x2=3 4x1+3x2-s1=6 x1+2x2+s2=4 xj≥0,j=1,2 s1≥0,s2≥0 来看标准型的约束系数矩阵 其中已经含有一个自然基底(0, 0,1)T,因此只需引入两个人工变量x3和x4,就可以形成一个初始可 行基I3(注:一个自然基底就是一个单位向量)。 Phase 1:引入人工变量x3和x4,通过求解线性规划问题 min w=x3+x4 s.t.3x1+x2+x3=3 4x1+3x2-s1+x4=6 x1+2x2+s2=4 s1≥0,s2≥0,xj≥0,j=1,2,3,4 寻找原问题的一个初始可行解。 下面编写SAS代码对Phase 1的问题进行求解。代码如下: proc optmodel; /*declare variables*/ var x1>=0, x2>=0, x3>=0,x4>=0,s1>=0,s2>=0; /*declare constraints*/ con con1: 3*x1+x2+x3=3; con con2:4*x1+3*x2-s1+x4=6; con con3: x1+2*x2+s2=4; /*declare objective*/ min w=x3+x4; solve with lp/solver=ps; print x1 x2 x3 x4 s1 s2; print x1.rc x2.rc x3.rc x4.rc s1.rc s2.rc; quit; 在上述代码中,首先用VAR语句声明了6个变量,其次用CON语句 声明了3个约束条件,分别命名为con1、con2和con3,然后声明了目标 函数w,并使用选项SOLVER=PS调用了单纯形法对该问题进行求解, 最后使用PRINT语句输出了最优解,后缀.RC表示REDUCED COST(即 判别系数)。输出结果如图20.5所示。 图20.5 例20.3Phase 1部分的输出 输出结果中Solution Status显示为Optimal,说明该问题已经达到最 优,最优解中人工变量x3和x4的取值都为0,并且目标最优值为0,说明 原LP问题存在可行解,x1=0.6,x2=1.2,s1=0和s2=1就是原问题的一个初 始基可行解。基变量x1和x2的REDUCED COST为0,非基变量x3、x4、 s1和s2的REDUCED COST都大于等于0,和前面在单纯形法中的分析结 果一样。 继续对原LP问题进行求解,代码如下: proc optmodel; /*declare variables*/ var x1>=0, x2>=0, x3>=0,x4>=0,s1>=0,s2>=0; /*declare constraints*/ con con1: 3*x1+x2+x3=3; con con2:4*x1+3*x2-s1+x4=6; con con3: x1+2*x2+s2=4; /*declare objective*/ min w=x3+x4; solve with lp/solver=ps; print x1 x2 x3 x4 s1 s2; print x1.rc x2.rc x3.rc x4.rc s1.rc s2.rc; con con4: x3+x4=0; min z=4*x1+x2; solve obj z with lp/solver=ps basis=warmstart; print x1 x2 x3 x4 s1 s2; print x1.rc x2.rc x3.rc x4.rc s1.rc s2.rc; quit; 这段代码中需要注意以下3个地方: ·调用了两次SOLVE语句,第一次调用的时候,求出了Phase 1的最 优解,第二次调用的时候,求出了原始LP问题的最优解。当在SOLVE 语句中不指定选项OBJ时,默认求解的目标函数是最近定义的目标函 数,也就是min z=4x1+x2。 ·在第二次求解前,引入了新的约束con4,这是因为在原LP问题 下,如果存在最优解,则x3+x4必须为0。 ·在第二次求解的过程中使用了选项BASIS=WARMSTART, WARMSTART使得在第二次求解时,告知系统从上一次求解得到的基 可行解出发,这样能够较快地得到最优解。在求解得到某LP问题的最优 解后,如果需要改变目标函数系数、修改约束条件右端项、增加或者减 少约束条件、增加或者减少决策变量,并重新进行求解,可以考虑使用 选项BASIS=WARMSTART,能够提高求解效率。 上述代码运行后的部分输出结果如图20.6所示。 图20.6 例20.3第二次调用SOLVE求解的部分输出 该输出结果显示原始问题的最优解为x1=0.4,x2=1.8,目标最优值 为3.4,各变量的REDUCED COST也暗示了该解为最优解。 上面两次调用了求解器来求解例20.3的LP问题,主要是为了展示和 解释两阶段单纯形法。其实SAS/OR在求解线性规划问题时,会将两阶 段法集成起来,而不需要用户进行标准型的转换,也不需要用户自行分 成两阶段进行求解。用户只需要在PROC OPTMODEL中声明原始的决 策变量、原始的约束条件和原始的目标函数,就可以对问题进行求解。 请看下面一段代码。 proc optmodel; var x1>=0, x2>=0; con con1: 3*x1+x2=3; con con2:4*x1+3*x2>=6; con con3: x1+2*x2<=4; min z=4*x1+x2; solve with lp/solver=ps; print x1 x2; quit; 代码中既有等式约束,也有≤和≥的不等式约束,最优解如图20.7所 示。可以看到,和图20.6中输出的最优解一样。 图20.7 例20.3最优解 20.3 20.3.1 对偶理论和灵敏性分析 对偶问题的导出 考虑如下LP问题: min z=cTx s.t.Ax=b x≥0 有最优解x*,其中A为m×n的矩阵,假设A的秩为m, 新的函数g(p),使g(p)= 构造一个 (cTx+pT(b-Ax)),p为m维列向量, 则有 g(p)≤cTx*+pT(b-Ax*)=cTx* 由于g(p)= (cTx+pT(b-Ax))=pTb+(cT-pTA)x,注意到 我们将新的LP问题 称为原LP问题(Primal) 的对偶问题 (Dual)。 从上面的推导过程可以看出,任何一个线性规划问题都有一个与之 相对应的线性规划问题,如果前者称为原问题,后者就称为对偶问题。 对偶问题是对原问题从另一角度进行的描述,其最优解与原问题的最优 解有着密切的联系。对偶理论就是研究线性规划及其对偶问题的理论, 是线性规划理论的重要内容。 来看一个著名的对偶问题——Diet Problem。 假设市场上有n种食品都含有m种营养素,其单位含量、单位价格 和人体每日对这m种营养素的需求如表20.4所示。试问消费者应如何选 购食物,既能满足人体每日对营养素的需求,又能够花费最少? 表20.4 营养素单位含量和单位价格数据 假设xj,j=1,…,n分别为对这n种食品的选购量,目标函数为每日 选购食品的花费,则可以建立以下线性规划模型: min z=c1x1+c2x2+…+cnxn s.t.a11x1+a12x2+…+a1nxn≥b1 (Primal) a21x1+a22x2+…+a2nxn≥b2 … am1x1+am2x2+…+amnxn≥bm xj≥0,j=1,2,…,n 再来考虑第二问题,若某厂商计划生产m种药丸,每种药丸含有一 个单位的某种营养素(m种营养素中的一种,且仅含一种营养素)。假 设yi,i=1,…,m分别为这m种药丸的单位售价,因此,该厂商生产药 丸的总收入为b1y1+b2y2+…+bmym=bTy,食品i含量相同营养素的药丸之 购买成本为a1iy1+a2iy2+…+amiym。如果药丸的价格小于同等含量的食品 价格,那么消费者会倾向于购买药丸,因此,厂商在考虑药丸定价的时 候,一定会使得药丸的价格比食品的价格便宜。因此有,a1iy1+a2iy2+… +amiym≤ci。同时,厂商也会考虑在这些条件下能够获取的最大收入,故 从厂商的角度,可以建立以下线性规划模型: max f=b1y1+b2y2+…+bmym s.t.a11y1+a21y2+…+an1yn≤c1 (Dual) a12y1+a22y2+…+an2yn≤c2 … a1ny1+a2ny2+…+annyn≤cn yi≥0,i=1,2,…,m 这两个LP问题中所使用的参数都是一样的,只是在模型中的结构不 一样,Diet problem是很经典的对偶问题。对照上面Primal和Dual中的约 束矩阵、决策变量和右端项的表示,不难得出二者的关联之处,如表 20.5所示。通常,我们将约束矩阵、决策变量和右端项具有这种关联的 两个线性规划问题称为对偶问题。 表20.5 对偶问题目标函数、约束系数、决策变量等之间的关系 除了上面提到的关联之外,如果在原问题中,约束条件是等式,则 在对偶问题中,对应的决策变量是自由的,没有非负约束。如果在原问 题中,某个决策变量是自由的,则在对偶问题中,对应的约束条件是等 式。 20.3.2 对偶问题的基本性质 若原问题为 其对偶问题为 则对偶问题具有以下基本性质。 ·弱对偶定理(Weak Duality):假设x和y分别为原问题(P)和其 对偶问题(D)的可行解,则一定有cTx≥bTy。 ·最优性定理:假设x和y分别为原问题(P)和其对偶问题(D)的 可行解,并且cT x=bT y,则x和y分别为原问题(P)和其对偶问题(D) 的最优解。 ·强对偶定理(Dual Theorem):若原问题(P)存在最优解x*,则 其对偶问题(D)一定存在最优解,对偶问题的最优解为y*=B-TcB,且 cTx*=bTy*(其中B为最优基,cB为最优解对应的目标系数向量)。 在实际线性规划问题中,原问题与其对偶问题是相对而言的,两者 互为对偶。可以证明,对偶问题的对偶问题就是原问题。 20.3.3 对偶单纯形法 回顾单纯形法,单纯形表如表20.6。从初始单纯形表开始,在进行 迭代的过程中,我们始终保持右端项的取值大于等于0,且通过进基准 则 选择进基变量,通过出基准则 选择出基变量,每一步 中都保持解的可行性,直到所有的非基变量的判别系数都大于等于0 时,达到最优解。 表20.6 单纯形表 但是,在实际求解线性规划问题时,有些情况下单纯形表中的右端 项向量并不都大于等于0,反而是所有的判别系数都大于等于0,这就相 当于当前解不是LP问题的可行解,如表20.7所示。 由对偶理论可知,在这种情况下,原问题的对偶问题是可行的。这 时可以使用对偶单纯形法进行操作,步骤如下: 1)确定出基变量。若所有的bi≥0,1≤i≤m,则原问题已经达到最 优;否则,假设i=r时, 则xr为出基变量。 2)确定进基变量。检查xr所在行的arj,若所有的arj≥0,则无解;若 存在arj<0,m+1≤j≤n,则 假设j=k时, 那么xk为进基变量。 表20.7 单纯形表(右端项含负值) 3)通过初等行变换进行换基运算,转入步骤1。 和单纯形法的出发点类似,对偶单纯形法的基本思想是保持对偶问 题的可行性,通过对进基变量和出基变量的选择,不断迭代,直到将原 问题的解由不可行变为可行为止,即右端项都大于等于0,此时原问题 也达到最优。因此,对偶单纯形法并不是求解对偶问题解的方法,而是 利用对偶理论求解原问题最优解的方法。 总的来讲,单纯形法是在基可行解中寻找满足最优性条件的最优 解,而对偶单纯形法则是在所有满足最优性条件的最优解中寻找满足可 行性条件的最优解。 对偶单纯形法通常使用在以下情形中: ·在原LP问题已经求得最优解之后,又在原LP问题中增加了新的约 束,要进行重新求解。 ·在原LP问题已经求得最优解之后,又更改了原LP问题中的右端项 向量,要进行重新求解。 在这两种情形中,在原问题已经达到最优解时,所有的判别系数都 是大于等于0,但是因为添加条件使得新问题的右端项较难判断,如果 右端项都大于等于0,则说明原问题的最优解就是新问题的最优解;如 果右端项不全大于等于0,说明原问题的最优解在新问题中已经不可 行,则正好使用对偶单纯形法进行求解。 SAS/OR的PROC OPTMODEL在求解LP问题时,对于存在最优解的 LP问题,不管其是调用单纯形法还是对偶单纯形法,都可以求得最优 解,只是在计算量上有些差别。系统默认使用对偶单纯形法,用户也可 以自己指定使用单纯形法还是对偶单纯形法,进行指定的语句如下: solve with lp/ solver=ds; 其中,选项SOLVER=用来指定求解线性规划的方法,前面在介绍 单纯形的时候已经使用过。选项SOLVER=的取值有4种:PS(单纯形 法)、DS(对偶单纯形法)、IP(内点法)、NS(网络单纯形法)。 在本章最后一部分,将简要介绍一下内点法的基本原理,在对大型 线性规划问题求解时,内点法具有较好的求解效果。网络单纯形法主要 用来求解一类特殊的线性规划问题,在第22章中,将会对此稍加介绍。 20.3.4 对偶问题的经济解释 考虑线性规划问题 假设这是一个利润最大化的生产计划问题,b 为各种资源的约束向量,A是m×n的矩阵,且矩阵A的秩为m。 在这个最大化问题中,当资源i增加一个单位(即资源i的资源限量 从bi增加到bi+1)而其他资源都不变时,所引起的目标函数最优值增加 的量则称为资源i的影子价格(shadow price)。 资源i的影子价格实际上就是资源i的边际利润。影子价格越大,说 明这种资源相对紧缺;影子价格越小,说明这种资源相对不紧缺;如果 最优生产计划下的某种资源有剩余,那么这种资源的影子价格一定等于 0。 影子价格可以通过以下方法求得。记B为该问题最优解对应的基矩 阵,即最优基,cB为基变量对应的目标系数向量,cN为非基变量对应的 目标系数向量,则该LP问题的最优解可表示为 令b=(0,…,0, 1,0,…,0)T,其中第i个分量为1,其余分量都为0,则目标函数最 优值的增量△z可记为 对于B-TcB这个表达式我们一定不陌生,在介绍强对偶定理的时 候,对偶问题的最优解即为y*=B-TcB。也就是说,对于形如 的线性 规划问题,各资源的影子价格就是其对偶问题的最优解。 在求解某些线性规划问题时,我们发现影子价格的符号可正可负, 这主要和约束条件不等式的方向(≤或者≥或者=)及优化目标(min或 者max)有关,并且具有如下规律: ·当优化目标为max时,方向为≤的约束条件具有非负的影子价格。 考虑两个优化目标都为max的LP问题,分别记为LP 1和LP 2。LP 1和LP 2具有相同的目标函数,并且LP 1的可行域包含在LP 2中,因此LP 1的 目标最优值一定小于等于LP 2的目标最优值。假设LP 1的最优解为x', 相应的最优值为z',则LP 2也可以取得z';并且由于LP 2的可行域更大, LP 2的最优值一定是大于等于z'。换句话说,在一个优化目标为max的 问题中,可行域的扩大一定不会使得最优值降低。结合影子价格的定 义,对于方向为≤的约束条件,如果增加右端项的取值,必然会导致可 行域的扩大,因此,影子价格必定为非负值。 ·当优化目标为max时,用与上面同样的分析方法可以得知,方向为 ≥的约束条件具有非正的影子价格。 ·当优化目标为max时,等式约束条件的影子价格可正可负。这也可 以理解,当等式约束条件的右端项改变时,整个可行域都发生了变化, 那么新的最优值可能增大也可能减小,则影子价格可能为正也可能为 负。 ·当优化目标为min时,方向为≤的约束条件具有非正的影子价格。 在一个优化目标为min的问题中,可行域的扩大一定不会使得最优值提 高,那么在增加右端项的取值之后,目标函数的增加量必然是个非正 值,故影子价格必定为非正值。 ·当优化目标为min时,用与上面同样的分析方法可知,方向为≥的 约束条件具有非负的影子价格。 ·当优化目标为min时,等式约束条件的影子价格可正可负。 在PROC OPTMODEL中,使用后缀.DUAL,可以输出约束条件的 影子价格,语法如下: print约束条件.DUAL; 需要注意的是,在PROC OPTMODEL中输出的影子价格的符号, 除了和约束不等式条件的方向及优化目标相关以外,还和约束不等式条 件在SAS代码中的表达方式相关,具体如下: ·如果决策变量都出现在约束条件不等式的左侧,且常数参数都出 现在不等式的右侧,如g(x)≤c或g(x)≥c,则影子价格的符号和上面 列举的规律一致。 ·如果决策变量都出现在约束条件不等式的右侧,且常数参数都出 现在不等式的左侧,如c≥g(x)或c≤g(x),那么影子价格的符号正好 与上面列举的规律相反。为了便于对影子价格的理解,建议尽量不要采 用该种代码书写方式。 ·如果决策变量同时出现在约束条件不等式的两侧,则影子价格的 符号仅依赖于不等式的方向,与上面列举的规律一样。 线性规划问题在给定资源下的生产计划安排中有着重要的作用。下 面来看一个生产计划问题的例子。 例20.4:某个体作坊生产甲、乙两种商品,每生产一件甲产品耗时 2个小时,每生产一件乙产品耗时1个小时;每件甲产品耗材1m3,每件 乙产品耗材1.5m3;每周中甲产品至少生产6件,且甲产品的市场最大需 求量是10件,乙产品的市场最大需求量是30件;每件甲产品的利润是 160元,每件乙产品的利润是120元。该作坊每周的工作时间是40小时, 用于生产甲、乙产品的原材料为50m3,问该作坊应如何安排生产计划才 能使得利润达到最大?该作坊工作时间的影子价格、原材料的影子价格 分别为多少?该作坊是否适合拓展甲产品和乙产品的市场需求? 假设该作坊每周生产x1件甲产品,生产x2件乙产品,那么可以建立 如下线性规划模型: Max z=160×x1+120×x2 s.t.2x1+x2≤40 (工时约束) x1+1.5x2≤50 (原材料约束) x1≥6 (甲产品最小生产量约束) x1≤10 (甲产品市场需求约束) x2≤30 (乙产品市场需求约束) x1≥0,x2≥0 (符号约束) 运用对偶单纯形法进行求解,代码如下: proc optmodel; var x1>=0, x2>=0; con time: 2*x1+x2<=40; con resource: x1+1.5*x2<=50; con demand1: x1>=6; con demand2: x1<=10; con demand3: x2<=30; max z=160*x1+120*x2; solve with lp/solver=ds; print x1 x2; print time.dual resource.dual demand1.dual demand2.dual demand3.dual; quit; 在上述代码中,使用CON语句进行约束条件声明的时候,分别将工 时、原材料、甲产品的最小生产量、甲产品的市场需求及乙产品的市场 需求5个约束条件命名为time、resource、demand1、demand2和 demand3,因此在输出影子价格时,就可以直接使用time.dual、 resource.dual、demand1.dual、demand2.dual和demand3.dual。如果在代 码中没有为约束条件命名,系统将自动按约束条件声明的顺序将约束条 件命名为_ACON_[1]、_ACON_[2]…,并存储在数组_ACON_中。 选项SOLVER=DS指定使用对偶单纯形法对该线性规划问题进行求 解,输出如图20.8所示。 图20.8 例20.4的部分输出 从输出结果看,该LP问题调用对偶单纯形法求解,经过4次迭代后 达到最优,每周生产6件甲产品和28件乙产品时,可以获得最大利润 4320元。工时约束的影子价格为120元,表示如果每周生产工时从40个 小时提高到41个小时,该作坊的总利润将提高120元。原材料的影子价 格为0,说明原材料仍有剩余(6+1.5×28<50)。甲产品和乙产品的最大 需求的影子价格也是0,这表明就目前的生产状况而言,该作坊若拓展 甲产品和乙产品的销售市场是没有意义的,不能给作坊带来更多的利 润。但是甲产品的最小需求量的影子价格为-80元,是一个负数,这是 因为该约束不等式为x1≥6,若将该约束的右端项增加1,即约束不等式 变为x1≥7,那么最大利润将减少80元。 20.3.5 灵敏性分析 在LP问题 中,当约束矩阵A的元素aij、右端项向量b的分量bi和目 标系数向量的分量cj分别发生变动时,其最优解或者最优值将会产生怎 样的变化呢?要回答这个问题,一种途径是对线性规划问题进行重新建 模,并运用单纯形算法进行求解。那么能否不重新求解,就可对上述变 化给出合理的判断和分析呢?可以的,事实上这就是我们要讨论的灵敏 性分析。 灵敏性分析在线性规划问题中具有很实际的意义。在很多条件下, LP问题中的参数(约束条件系数、右端项或目标系数)会发生一定的改 变。例如,在例20.4中,甲产品和乙产品的利润发生了改变,或者原材 料的数量发生了改变。当这些参数发生改变时,通过灵敏性分析即使不 进行重新求解,也可以分析判断对生产计划将会造成哪些影响。当原材 料数量增加1m3时,当前解仍然是最优解。对于小规模的线性规划问 题,重新求解可能不会花费多少计算量,但是对于一个具有成千上万个 决策变量和约束条件的线性规划问题,重新求解的计算量是非常可观 的。 在LP问题 中,不失一般性,假设A为m×n的系数约束矩阵,矩阵 A的前m列线性无关,则A可以分块表示为A=(B N),B为最优基。 相应的,x也可分块记为 xB为基变量,xN为非基变量,cB为基变量的 目标系数,cN为非基变量的目标系数,则最优单纯形表如表20.8的右边 部分所示。 表20.8 推导最优单纯形表 因为表20.6中的右边部分是最优单纯形表,因此,非基变量的判别 系数cTN-cTBB-1N≥0,并且最优解B-1b≥0。所以,当约束矩阵A的元素 aij、右端项向量b的分量bi和目标系数向量的分量cj分别发生变动时,由 上面的最优单纯形表就可以分析出对最优性、可行性及目标值的可能影 响。可进行如下归纳: 1)当右端项向量b的分量bi变动时,B-1b将发生变化,cTBB-1b也会 变化,判别系数cTN-cTBB-1N不受影响,因此当前解的最优性不会改变, 但是当前的可行性和目标值会受到影响。 2)当目标系数向量的分量cj发生变动,且cj是cB的分量时,cTNcTBB-1N可能会改变,那么当前解的最优性将可能会受影响,同时目标 值也会改变,但是当前解的可行性不会受影响。 3)当目标系数向量的分量cj发生变动,且cj是cN的分量时,最优性 将可能会受影响,当前解的可行性和目标值都不会受影响。 4)当约束矩阵A的元素aij发生变动时,且aij是矩阵N中的元素时, 最优性将可能会受影响,当前解的可行性和目标值都不会受影响。 5)当约束矩阵A的元素aij发生变动时,且aij是矩阵B中的元素时, 情况比较复杂,需视情况而定。 在第22章中将具体介绍当这些参数发生变化时在PROC OTPMODEL中进行模型更新的操作。 20.4 内点法 在计算科学中,算法的时间复杂度是一个函数,它定量描述了该算 法的运行时间。时间复杂度常用符号O表述,不包括这个函数的低阶项 和最高阶项的系数,常见的时间复杂度有线性阶O(n)、平方阶 O(n2)和立方阶O(n3)等。这里n、n2、n3都是关于n的多项式,记为 p(n)。时间复杂度是O(p(n))的算法称为多项式时间算法。不能 够由n的多项式控制时间复杂度的算法被称为指数时间算法。 1972年,V.Klee和G.Minty构造出一个线性规划问题的例子,如果 使用单纯形方法进行求解,迭代过程需要的计算时间复杂度为 O(2n),这个例子意味着,在理论上,随着问题规模n的不断增大,单 纯形法的时间复杂度将呈指数阶增长,但它并不是一个多项式算法。也 就是说,单纯形法虽然在实际应用中非常有效,并且占有绝对优势,但 是在最坏的情况下,随着约束条件和决策变量的增加,它求解所需的迭 代次数将随着问题规模的上升呈指数倍增长,收敛很慢。 内点法(Interior-Point Method)在这一方面具备突出的优点。内点 法是一种求解线性规划与非线性凸优化的算法,它是完全不同于单纯形 法的一类算法,属于多项式时间算法。第一个将内点法用于线性规划问 题的是贝尔实验室的Karmarkar,他在1984年发表的论文中率先提出 了“内点”迭代的思想,在可行域内部进行寻优计算,从而得到下一个迭 代点。数值计算表明,对于大型的实际应用问题,内点法具有与单纯形 法一争高下的潜力,正因为如此,内点法至今仍是学术界研究的热点之 一。 Karmarkar提出的现代内点算法是建立在单纯形结构上的,但是与 单纯形法沿着可行域边界寻找最优解不同,内点法是从初始内点出发 的,沿着最速下降方向,从可行域的内部走向最优解。内点法是在可行 域内部寻找最优解。 现代内点算法主要分以下3类: ·投影尺度法(Projective Scaling) ·仿射尺度法(Affine Scaling) ·路径跟踪法(Path Following) 对于大规模线性规划问题,随着约束条件和决策变量数目的增加, 现代内点法的迭代次数变化较少。作为一种具有多项式时间复杂度的线 性规划算法,内点法的收敛性和计算速度均优于单纯形法,并且可以将 其应用于非线性问题的求解中。 PROC OPTMODEL中使用内点法求解线性规划问题时需使用选项 SOLVER=INTERIORPOINT,也可以简写成SOLVER=IP。 注意 在小规模问题中,内点法在迭代次数上可能比图解法、单 纯形法或者对偶单纯形法多,但是在大型问题中,内点法具有很大的优 势。 20.5 本章小结 本章介绍了线性规划问题的基本理论,包括线性规划问题的定义、 标准型、标准型的转换技巧、对偶理论、灵敏性分析等知识,还讨论了 求解线性规划问题的4种基本方法:图解法、单纯形法、对偶单纯形法 以及具有多项式时间复杂度的现代内点法。在对这些基本理论的介绍过 程中,还结合示例,向读者展示了后3种方法在SAS/OR的PROC OPTMODEL中的调用方法,并就输出结果进行了解释说明。 第21章 运用PROC OPTMODEL建立线性规划模型 在前一章中,讨论了线性规划的基本理论和常用算法,并结合 PROC OPTMODEL的简单示例介绍了在SAS中运用求解器调用这些算法 的方式,并对基本的输出结果进行了解读。本章主要介绍如何运用 PROC OPTMODEL建立线性规划模型,包括PROC OPTMODEL中的基 本概念、过程步的基本结构及建模时各种语句的使用方法和表达方式。 这部分内容对于运用PROC OPTMODEL解决优化问题来说非常重要, 因为只有首先将优化模型以正确的方式表达出来,才能在之后的求解过 程中选择合适算法进而寻找最优解。本章最后还将介绍如何在PROC OPTMODEL中读取和创建数据集。 21.1 基本概念 为了表述方便,在本章的开始先介绍PROC OPTMODEL中涉及的 基本概念。 21.1.1 参数 在例19.1中介绍PROC OPTMODEL的例子时,我们把所有已知的数 值型常数直接用数字的方式表示在目标函数和约束条件表达式中。对于 小型的优化问题,这是一种合理的做法,程序的编写也相对简单。但是 更多时候,这种做法会带来很多不便,一方面它使得约束条件或者目标 函数的意义不够直观,不利于展示模型的基本结构;另一方面对模型的 修改也非常不易,尤其是对于大型优化问题来说。 在PROC OPTMODEL中,可以将这些数值型常数表示为不同的参 数,在编写目标函数或者约束条件的表达式时则使用参数的名称代替原 始的数字。在这种方式下,就可以通过修改参数的取值达到修改模型的 效果。 在求解的过程中,参数的取值不会发生改变,因此目标函数系数、 约束条件系数和右端项都可以用参数表示。在PROC OPTMODEL中, 参数的取值可以在PROC OPTMODEL中直接赋值,也可以通过读取数 据集获得。 21.1.2 索引和索引集 索引集是一个集合,在PROC OPTMODEL中,我们把这个集合中 的元素称为项。在PROC OPTMODEL中,索引集的运用非常广泛,提 高了模型的可读性。 很多优化模型中含有大量的决策变量或参数,根据不同的用途,这 些决策变量或者参数往往可以分成不同的类别,例如表示不同产品价格 的参数和表示不同产品成本的参数就可以归类为两个不同的参数类别, 即价格和成本,每个类别可以定义为一个对应的参数数组。参数数组中 的每一个元素都包含两个部分,一部分为该元素的标识或位置,另一部 分为该元素的取值。在声明这样的参数数组时,就需要使用索引集了, 用来自一个或多个索引集的项去定义数组中每个元素的标识或位置。 PROC OPTMODEL中索引集的项可以是数字或者字符。 SET语句用来声明索引集,在下面的代码中,首先声明了一个索引 集PRODUCT,并且基于这个索引集定义了表示产品售价的参数数组 Price。 proc optmodel; set<string> PRODUCT=/carA carB/; num Price{PRODUCT}; quit; 其中,SET语句声明了一个索引集,这个集合包含两个项carA和 carB;NUM语句声明了一个参数数组Price,根据索引集PRODUCT中的 项,参数数组Price包含两个元素,分别标识为carA的参数和carB的参 数,每个元素尚未赋值。在对数组赋值之后,参数数组Price所存储的就 是产品carA和carB的价格了。 下面的代码用另外一种形式声明了一个基于索引集的参数数组,该 索引集的项为数字。 num P{1..3}; NUM语句表示声明数值型参数数组,“..”是范围表达式。“1..3”表示 从1至3的整数1、2、3。索引集{1..3}中包含3个项,分别为1、2和3,该 语句声明了一个参数数组P,P为索引集中的每个项都分配了一个固定的 位置存放数值型参数。 索引集不仅可以用在参数数组中,也可以用在目标函数、约束条 件、决策变量、隐式决策变量中。 21.1.3 数据类型 在PROC OPTMODEL中,参数的取值和表达式中可以含有数值或 者字符串,对应的数据类型为NUMBER和STRING。NUMBER类型表示 数值型,和数据集中的数值型一样,并且可以简写成NUM;STRING类 型表示字符型,和数据集中的字符类型一样,但是在PROC OPTMODEL中,字符串的长度可以达到65534个字符(数据集中的字符 串长度最大为32767个字符)。NUMBER类型和STRING类型统称为标 量类型。 在PROC OPTMODEL中,参数只有两种类型,一种是数值型参 数,一种是字符型参数。集合的类型则有很多种,其中项的数据类型除 了可以是数值或者字符外,也可以是元组(Tuple),但是在同一个集 合中,项的数据类型必须一致。元组的表达形式如下: <表达式1,…表达式n> 一个元组用一对尖括号括起来,当有n个表达式时,表示这是一个n 元组,每个表达式中间用逗号隔开。 如果集合中的项是元组,则每个项必须都是相同类型的元组。所谓 相同类型的元组,指的是每个元组对应位置的数据类型必须一样。例 如,下面一段代码声明了集合PART: set <string,number> PART; PART中的每个项都是一个二元组,在这个二元组中,第一个位置 的数据类型为STRING,第二个位置的数据类型为NUMBER。 21.1.4 名称 在PROC OPTMODEL中,决策变量、参数、约束条件或者目标函 数等的命名规则和DATA步中一样,即:长度最长为32个字符,必须以 字母开始,可以是数字、字母和下划线的任意组合,并且名称中字母的 大小写不敏感。需要注意的是,为了与避免PROC OPTMODEL中的保 留名称冲突,命名的时候建议不要以下划线开头。 为了增加程序的可读性,在进行命名的时候,名称应尽量直观、简 洁。 21.1.5 表达式 在PROC OPTMODEL中,表达式根据返回值的类型可以分为3种: 逻辑表达式、集合表达式和数值表达式。 ·逻辑表达式,返回值是逻辑值1或者0。PROC OPTMODEL中逻辑 表达式的表达方式、运算逻辑和优先级与DATA步中的基本一致。但 是,在PROC OPTMODEL中,NOT操作符的优先级比关系操作符(如 >,<等)低,例如,not 1<2等价于not(1<2)。 ·集合表达式,返回值是一个集合。在第22章中,将重点介绍PROC OPTMODEL中的各种集合运算。 ·数值表达式,这里说的数值包含数值型和字符串型,返回值可以 是数值或者字符串。数值表达式的操作数可以是变量、参数,也可以是 常数,操作符主要包括“||”、“+”、“-”、“*”、“/”,以及后面介绍的SUM 集成表达式、MAX集成表达式、MAX集成表达式等。常见的数值表达 式的例子有x+y、x、3、trim(last)||'or'||first等。和DATA步不一样的 是,PROC OPTMODEL中字符的长度是动态定义的,因此不会出现截 断的情况。 21.1.6 标识表达式 在PROC OPTMODEL中,参数、决策变量、目标函数、约束条件 等在大多数情况下都是基于索引集声明的,因此,大部分都是数组形 式,使用标识表达式(Identifier Expressions)可以定位到数组中的具体 位置。使用标识表达式的语法如下: 名称[[表达式1[,…表达式n]]]; 其中,名称可以是参数数组的名称、决策变量组的名称、目标函数 组的名称、约束条件组的名称或者隐式变量组的名称。[表达式1,…表 达式n]指定数组内部的具体标识,表达式1、…、表达式n返回的结果是 定义数组时使用的索引集中的项。 下面一段代码声明了参数数组A和参数数组Price,A的索引集中含 有两个二元组项<1,2>和<3,4>,Price的索引集中含有carA和carB两个 项。 proc optmodel; set <num,num> ISET={<1,2>,<3,4>}; number A{ISET}; set<string> PRODUCT=/carA carB/; num Price{PRODUCT}; A[1,2]=0; /*valid*/ A[3,2]=0; /*invalid index*/ Price[carA]=10,000; /*carA的价格为10,000美元 */ quit; 其中,第一个赋值语句中的标识表达式A[1,2]是正确的;第二个 赋值语句中的标识表达式A[3,2]则不正确,因为<3,2>不是A的索引 集ISET中的项,所以数组A没有A[3,2]这个元素;第三个赋值语句为 将carA的价格赋值为10000。 21.1.7 函数表达式 大部分在DATA步或者通过宏%SYSFUNC可以使用的函数在PROC OPTMODEL中也可以使用,但也有一些可以在DATA步中使用的函数 在PROC OPTMODEL中不可以使用,这些函数可以分为以下3类: ·LAG函数、DIF函数和DIM函数 ·涉及DATA步中PDV的函数 ·涉及符号属性的函数 21.1.8 索引集的补充 一个或者多个集合可以通过某种形式组成一个新的集合,这种方法 主要用在以集合作为参数、目标函数和约束条件等的索引集中。语法如 下: {索引集1[,索引集2,…,索引集n][:逻辑表达式]}; 注意 为了和SAS/OR的帮助文档一致,在介绍语法的时候,将 可选的要素或者选项用[*]来表示。 所有的索引集都用“{}”括起来,每个索引集之间用逗号分隔,这个 表达式返回的集合是由索引集1、…、索引集n交叉相乘之后得到的集 合。其中,每个索引集可以用如下3种形式之一表示: 集合表达式 虚拟参数 IN集合表达式 <虚拟参数1[,…,虚拟参数n]> IN 集合表达式 其中: ·第一行仅返回一个集合; ·第二行表明集合表达式中的项是一元组,关键字IN声明了一个虚 拟参数,通过虚拟参数可以引用集合中的项。 ·第三行表明集合表达式中的项是一个n元组,关键字IN在声明虚拟 参数的同时,必须声明n个虚拟参数,并使用<>将这些虚拟参数括起 来,每个虚拟参数之间用逗号分隔。通过虚拟参数1、…、虚拟参数n可 以依次引用集合中的项对应位置的数值。 虽然虚拟参数的作用和参数一样,但是作用范围不一样,它只能在 声明它的索引集之后的表达式中使用,后面在用到虚拟参数时会具体介 绍。例如,这里的虚拟参数可以使用在“:”后面的逻辑表达式中,逻辑 表达式主要用来对新索引集中的项进行过滤和选择。 下面一段代码演示了集合INDXA和INDXB通过“{}”生成了一个新 的集合。集合INDXA中的项为1、2、3、4,集合INDXB中的项为<1, x>和<1,y>。 proc optmodel; set<num> INDXA=1..4; set<num,str> INDXB=/<1,x><1,y>/; put ({a in INDXA,<b,c> in INDXB}); quit; 在日志中可以看到,“{a in INDXA,<b,c>in INDXB}”生成的新的 集合如下: {<1,1,'x'>,<2,1,'x'>,<3,1,'x'>,<4,1,'x'>,<1, 1,'y'>,<2,1,'y'>,<3,1,'y'>,<4,1,'y'>} 在PROC OPTMODEL中,有一类特殊的基于索引集的表达式,叫 做集成(Aggregation Expression),使用语法如下: 集成算子{虚拟参数 IN 索引集} 表达式; 这类集成表达式在计算的时候,将使虚拟参数一一遍历索引集中的 项,这里的虚拟参数可以在后面的表达式中使用。常见的此类表达式有 SUM集成表达式、MAX集成表达式、MIN集成表达式、SETOF集成表 达式、UNION集成表达式等。 SUM集成表达式(SUM Aggregation Expression)可以用来叠加由 数值、决策变量、隐式变量或者参数组成表达式,在表达式中SUM集成 算子的优先级高于“+”算子和“-”算子,但是低于“()”、指数算子、“*”算 子和“/”算子。示例代码如下: print (sum{k in 1..24} k**2); 代码中在索引集{k in 1..24}中声明了一个虚拟参数k。当索引集中 的项一个个被遍历时,索引集中的项将被一一赋值给虚拟参数,并将 SUM集成表达式中的表达式(k**2)逐项叠加,因此该行代码输出的结 果为1**2、2**2、…、24**2相加之和,即4900。注意,虚拟参数只能 在索引集的控制范围内使用。 UNION集成表达式(UNION Aggregation Expression)用来返回由 表达式计算出来的数字或者集合的并集。示例代码如下: proc optmodel; put (union{i in 1..3} i..i+3); quit; 在日志中,可以看到返回的数字集合为{1,2,3,4,5,6},也就 是{1,2,3,4}{2,3,4,5}{3,4,5,6}。 MAX集成表达式、MIN集成表达式和SETOF集成表达式将在第22 章中具体介绍。 21.2 基本结构 运用PROC OPTMODEL建立和求解优化模型的基本结构如下: proc optomdel; /*declare sets and parameters*/ /*declare variables*/ /*declare constraints*/ /*declare objective*/ /*call solver*/ /*print solution*/ quit; 其中主要包含以下6个要素: ·索引集和参数的声明 ·决策变量的声明 ·约束条件的声明 ·目标函数的声明 ·求解器的调用 ·数据输出 运用PROC OPTMODEL建立模型的表达方式很灵活,例如,目标 函数的声明可以在约束条件的声明之前,也可以在其之后,并且这里列 出的基本要素并不是每一个模型都必须有的。 下面通过一个示例来演示运用PROC OPTMODEL建模的基本结 构。 例21.1:某面包房主要制作4种面包和糕点,分别为可颂、吐司、 法式面包和曲奇饼干,原料主要为面粉、奶油和黄油。每一烤箱面包或 糕点的售价及所需的原材料如表21.1所示,面粉、奶油和黄油的售价及 每天可供使用量如表21.2所示。 表21.1 面包糕点用料需求 表21.2 原料成本和供给量 假设每天制作的面包和糕点都能全部售完,那么对于各种面包和糕 点,每天分别需要制作多少箱才能使得面包房总的盈利最大? 对于这个问题,根据第20章中例20.1的分析方法来看,这里的决策 变量分别为可颂、吐司、法式面包和曲奇饼干的制作量;目标函数是利 润,利润=收入-成本,优化目标是最大化利润函数;约束条件为原料面 粉、奶油和黄油的供给量。 对照以上6个要素,建立线性规划模型的最简单示例代码如下: proc optmodel; /*declare variables*/ var Croissant>=0,Toast>=0,Baguette>=0,Cookie>=0; /*declare constraints*/ con Flour: 3*Croissant+3*Toast+4*Baguette+3.5*Cookie<=130; con Cream: 2*Croissant+1.5*Toast+0.5*Baguette+2*Cookie<=60; con Butter: 2*Croissant+Toast+Baguette+Cookie<=40; /*declare objective*/ max Profit=440*Croissant+330*Toast+315*Baguette+385*Cookie -20*(3*Croissant+3*Toast+4*Baguette+3.5*Cookie) -40*(2*Croissant+1.5*Toast+0.5*Baguette+2*Cookie) -35*(2*Croissant+Toast+Baguette+Cookie); /*call solver*/ solve; expand; /*print solution*/ print Croissant Toast Baguette Cookie; quit; 在PROC OPTMODEL中,任何参数和变量在使用变量之前,都必 须先声明。这里为了增加模型的可读性,使用Croissant、Toast、 Baguette和Cookie分别表示可颂、吐司、法式面包和曲奇饼干的制作 量,即问题中的决策变量。 EXPAND语句用来在结果窗口中输出线性规划模型的决策变量、目 标函数和约束条件。使用EXPAND语句的基本语法如下: EXPAND 名称 [/EXPAND选项] ; 其中,名称可以是决策变量名称、目标函数名称、隐式变量名称或 者约束条件名称。在PROC OPTMODEL中,如果某些相同的表达式在 同一模型中多次出现,为了使得代码简洁,可以将相同的表达式定义成 隐式变量,这样,在这些表达式出现的时候就可以用隐式变量代替了 (稍后将介绍声明隐式变量的语法)。“/”后的EXPAND选项包括如下4 种(这4种选项可以同时使用,也可以单独使用): ·VAR表示输出所有决策变量。 ·IMPVAR表示输出所有隐式变量的表达式。 ·OBJECTIVE表示输出所有目标函数的表达式,简写为OBJ。 ·CONSTRAINT表示输出所有约束的表达式,简写为CON。 在EXPAND语句中,当名称和EXPAND选项都采用默认形式时,系 统默认输出所有的决策变量、隐式变量、目标函数和约束条件,作用等 同于下面的代码: EXPAND /VAR IMPVAR OBJ CON; 例21.1中所建模型的部分输出如图21.1所示。 图21.1 例21.1的部分输出内容 因为使用了EXPAND语句,图21.1的中间部分输出了模型的决策变 量、目标函数和约束条件。 在上面的代码中,由于表达式 3*Croissant+3*Toast+4*Baguette+3.5*Cookie、 2*Croissant+1.5*Toast+0.5*Baguette+2*Cookie、 2*Croissant+Toast+Baguette+Cookie都分别出现过两次,为了实现程序的 简洁可读性,考虑使用隐式变量代替这3个表达式。 使用IMPVAR语句声明隐式变量的基本语法如下: IMPVAR 隐式变量名称 = 表达式; 隐式变量的命名规则和决策变量一样。 在例21.1中,运用隐式变量代替重复出现的表达式。示例代码如 下: proc optmodel; /*declare variables*/ var Croissant>=0,Toast>=0,Baguette>=0,Cookie>=0; impvar Kilos = 3*Croissant+3*Toast+4*Baguette+3.5*Cookie; impvar Bottles=2*Croissant+1.5*Toast+0.5*Baguette+2*Cookie; impvar Bags=2*Croissant+Toast+Baguette+Cookie; /*declare constraints*/ con Flour: Kilos<=130,Cream: Bottles<=60,Butter: Bags<=40; /*declare objective*/ max Profit=440*Croissant+330*Toast+315*Baguette+385*Cookie -20*Kilos-40*Bottles-35*Bags; solve; expand/impvar; /*print solution*/ print Croissant Toast Baguette Cookie; quit; 代码中运用IMPVAR语句声明了3个隐式变量Kilos、Bottles和 Bags,并在约束条件和目标函数中,分别用这3个隐式变量代替了原来 的表达式,最后运用EXPAND语句输出了隐式变量的表达式,如图21.2 所示。 图21.2 隐式变量表达式 21.3 建立模型 接下来将一一介绍PROC OPTMODEL中6个要素的基本使用方法。 21.3.1 参数的声明 在PROC OPTMODEL中,参数(包含参数数组)在使用前都必须 进行声明。 1.数值型参数的声明 NUMBER或者NUM语句都可以用来声明数值型参数,如: num N; num Pi=constant('pi'); num M{1..N,1..N} init 0; 其中: ·第一行代码声明一个名称为N的数值型参数。 ·第二行代码声明了一个名称为Pi的数值型参数,并且给该参数赋值 为常数。 ·第三行代码声明了一个名称为M的二维的N×N的参数数组,并且 给该参数数组中每个位置的值都赋予初值0。“{}”是一个集合表达式, 返回一个集合;“1..N”是一个范围表达式,表示返回从1到N的所有数 字,“{1..N,1..N}”合起来表示一个组成项为二元组的索引集{1..N, 1..N},其中的项分别为<1,1>,<1,2>,…,<1,N>,<2,1>, …,,…,。 2.字符型参数的声明 STRING或者STR语句可以用来声明字符型参数,如: str Name; str Day{1..7}=[Mon Tue Wed Thu Fri Sat Sun]; str Type{Resource}; 其中: ·第一行代码声明了一个名称为Name的字符型参数。 ·第二行代码声明了一个名称为Day的字符型参数数组,等于号后面 使用“[]”表示为数组赋值,初值为Mon、Tue、Wed、Thu、Fri、Sat和 Sun。比如,标识表达式Day[5]就表示该参数数组中的索引为5的参数 值,这里即为Fri。 ·第三行代码声明了一个名称为Type的字符型参数数组,参数数组 中各个元素的标识来自索引集Resource。 3.集合的声明 SET语句用来声明集合,一个SET语句可以同时声明多个集合,每 个集合中间用逗号分隔。集合中的项可以是一个一个的数值,或者是一 个一个的字符串,也可以用数值或者字符串共同表示二元组或者多元 组。常见的几种集合声明代码如下: set set set set <num> DOW=1..7; <str> COLOR=/red orange yellow green blue/; <str,num> PARTS=/<R,1><C,2>/; <str,str> ARCS; 其中: ·第一行代码声明了一个名称为DOW的集合,中的关键字num指定 了集合中项的取值类型为数值型,等号后面为该集合赋予初值1、2、 3、4、5、6、7。PROC OPTMODEL中默认的数据类型为NUM。 ·第二行代码定声明了一个名称为COLOR的集合,中的关键字str表 示该集合中每个项的取值类型为字符型,“//”表示为索引集赋值,该索 引集的初值为red、orange、yellow、green和blue。需要注意的是,使 用“//”为集合赋字符型值时,如果字符串符合SAS的变量名称规则,则 可以不用引号引起来,如上面的COLOR=/red orange yellow green blue/, 但是,如果字符串是以数字开头或者中间含有空格、标点等特殊符号, 则字符串需要用引号引起来。 ·第三行代码声明了一个名称为PARTS的集合,表示集合中每个项 的取值为一个二元组,二元组中第一个位置的数据是字符型,第二个位 置的数据是数值型,集合PARTS中的项为和。 ·第四行代码声明了一个名称为ARCS的二元组,该二元组中两个位 置的数组都是字符型。 使用SET语句可以声明项为多元组的集合,语法如下: set <type1,type2,...,typen> INDEXING; 其中type1、type2、...、typen代表关键字num或者str,并且互不影 响。默认情况下,集合中项的取值类型为数值型,如果在声明集合的同 时对集合赋初值,那么系统会根据初值的类型自动辨别集合中该项的取 值类型,此时就可以省略类型声明<num>、<str>或者<num,str>等。注 意,当集合中的项为多元组时,若使用“//”表达式为集合赋值,每一项 都需要用尖括号<>括起来。 注意 在PROC OPTMODEL中,SET语句的使用方法和DATA步 中SET语句完全不一样,请不要将其相互混淆。 那么,对于例21.1中的问题,则可以定义索引集PRODUCT和 RESOURCE,并且根据索引集定义参数数组Selling_Price、Cost、 Available和Requirement。示例代码如下: set num set num num num PRODUCT=/croissant toast baguette cookie/; Selling_Price{PRODUCT}=[440 330 315 385]; RESOURCE=/flour cream butter/; Cost{RESOURCE}=[20 40 35]; Available{RESOURCE}=[130 60 40]; Requirement{PRODUCT,RESOURCE}=[3 2 3 1.5 4 0.5 3.5 2 /*line 1*/ /*line 2*/ /*line 3*/ /*line 4*/ /*line 5*/ 2 1 1 1]; /*line 6*/ 其中: ·第一行代码声明了索引集PRODUCT,并赋予了初值,因为这里直 接赋初值,所以索引集中项的取值类型声明可以省略。 ·第二行代码基于已经声明的索引集PRODUCT,声明了参数数组 Selling_Price,根据索引集PRODUCT中项的顺序croissant、toast、 baguette和cookie,Selling_Price[croissant]、Selling_Price[toast]、 Selling_Price[baguette]和Selling_Price[cookie]的取值分别为440、330、 315和385。 ·第三行代码声明了索引集RESOURCE,并赋予初值。 ·第四行和第五行代码分别声明了两个一维数组Cost和Available,并 赋予初值。 ·第六行代码基于索引集PRODUCT和RESOURCE声明了一个二维 数组Requirement,PRODUCT为第一个维度(行),RESOURCE为第二 维度(列)。其实,在赋值时所有的数字都可以在一行中输入,这里分 成4行是为了增加代码可读性。 例21.2:某银行拟从北部、东部、南部和西部4个地区分别抽取一 部分各个年龄段的客户进行促销活动。每个地区每个年龄段欲抽取的客 户数量如表21.3所示。现要将下列数据导入PROC OPTMODEL的参数 中。 表21.3 某银行分地区分年龄段的目标客户数量 这里可以定义两个索引集AREA和AGE,基于这两个索引集定义一 个参数数组来存储分地区分年龄段的目标客户数量。示例代码如下: proc optmodel; set AREA=/'北部''东部''南部''西部'/; set AGE=/'18-25岁''26-35岁''36-45岁''46-55岁''56岁及以上'/; num Target{AREA,AGE}=[1200 3200 2000 800 800 1350 3600 2250 900 900 5250 1400 8750 3500 3500 1200 3200 2000 800 800]; print Target; quit; 给索引集AREA和AGE赋初值时,每个字符串都要用引号引起来, 这是因为这里使用了中文字符,不符合SAS的命名规则。在表21.4中, 每一行代表相同的地区,不同的列代表不同的年龄段,为了和该表的表 现形式统一,在声明二维数组Target时,用AREA作为第一维度 (行),用AGE作为第二维度(列)。在代码的最后运用PRINT语句将 参数Target输出到“结果”窗口,如图21.3所示。 图21.3 21.3.2 例21.2输出内容 变量的声明 1.决策变量的声明 声明决策变量的VAR语句的基本语法和声明参数数组的语法类似, 如下: VAR VAR声明 [, VAR声明, … ,VAR声明]; 一个VAR语句中可以有多个VAR声明,由逗号隔开。每个VAR声 明由3部分组成,一部分是决策变量名称,一部分是索引集,一部分是 VAR选项。语法如下: VAR 名称[{索引集}][VAR选项]; 可用的VAR选项有如下5种。 ·>=表达式:表示为决策变量的取值设定一个下界,默认情况下, 决策变量的下界为-∞。 ·<=表达式:表示为决策变量的取值设定一个上界,默认情况下, 决策变量的上界为∞。 ·INIT表达式:表示为决策变量设定一个初始值,默认情况下,初 始值为0。 ·INTEGER:表示决策变量的取值只能取整数。 ·BINARY:表示决策变量的取值只能是0或者1。 下面一段代码声明了两个决策变量X和Y,并为变量X赋上了初始值 0.5,同时设定变量X和Y的上下界分别为0和1。 var X init 0.5 >=0 <=1, Y >=0 <=1; 在例21.1中,可以运用已经定义的索引集来声明决策变量。代码如 下: var X{PRODUCT}>=0; 这里的决策变量X其实是一个决策变量组,根据索引集PRODUCT 中的项,我们有不同产品的决策变量,如X[croissant]、X[toast]、 X[baguette]和X[cookie]。和参数、索引集一样,决策变量也必须先声明 后使用。 2.隐式变量的声明 前面介绍了声明隐式变量的简单语法,和决策变量一样,隐式变量 的声明也可以基于索引集。基本语法如下: IMPVAR 隐式变量名称[{索引集}] = 表达式; 其中,右边表达式可以包含决策变量、常数、参数或者其他隐式变 量。在索引集中声明的虚拟参数也可以在右边的表达式(包括标识表达 式)中使用。 在例21.1中,声明隐式变量Amount_Used{RESOURCE}。代码如 下: impvar Amount_Used{r in RESOURCE}=sum{p in PRODUCT}Requirement[p,r]*X[p]; IMPAVR语句声明了一个索引集为{RESOURCE}的隐式变量组。当 RESOURCE中的项一个个被遍历时,RESOURCE中的项将一个个被赋 给虚拟参数r,r可以使用在右边参数数组Required的标识表达式中。上 面这段代码的作用和下面的代码一样,相当于定义了3个隐式变量。 Impvar Amount_Used['flour']=sum{p in PRODUCT}Requirement[p,'flour']*X[p]; Impvar Amount_Used['cream']=sum{p in PRODUCT}Requirement[p,'cream']*X[p]; Impvar Amount_Used['butter']=sum{p in PRODUCT}Requirement[p,'butter']*X[p]; 如此,在例21.1中声明的3个隐式变量Kilos、Bottles和Bags就变成 了这里的Amount_Used['flour']、Amount_Used['cream']和 Amount_Used['Bags']。 21.3.3 目标函数的声明 进行目标函数声明的基本语法如下: maximize|max目标函数名称[{索引集}]=表达式; minimize|min目标函数名称[{索引集}]=表达式; MAXIMIZE|MAX和MINIMIZE|MIN声明了优化方向和目标函数。 目标函数名称不能和变量名称或者参数名称重复。在一个PROC OPTMODEL中,可以声明多个目标函数,但是求解器每次只能求解含 有一个目标函数的优化问题。索引集中声明的虚拟变量在等号右边表达 式中可以使用。 21.3.4 约束条件的声明 进行约束条件声明的基本语法如下: CONSTRAINT 约束条件声明1[,…,约束条件声明n]; 这里,CONSTRAINT可以简写为CON。一个CONSTRAINT语句可 以声明多个约束条件,每个约束条件之间用逗号分隔。 每个约束条件声明由3部分组成,一部分是约束条件名称,一部分 是索引集,一部分是表达式。约束条件声明有如下3种形式: CONSTRAINT [约束条件名称[{索引集}]:]表达式=表达式; CONSTRAINT [约束条件名称[{索引集}]:]表达式 relation表达式; CONSTRAINT [约束条件名称[{索引集}]:]边界1 relation 表达式relation 边界2; 这里的relation指的是>=或者<=,在上面的第三个表示范围的约束 条件中,两个relation必须是同方向的。如果在进行约束条件声明时,没 有为约束条件命名,默认情况下,系统将根据约束条件出现的顺序依次 将其命名为_ACON_[n]。 在例21.1中,可以将约束条件Flour、Cream和Butter改写成如下形 式: con Usage{r in RESOURCE}: Amount_Used[r]<=Available[r]; 如此,原来的约束条件flour、cream和butter依次变成了 Usage['Flour']、Usage['Cream']和Usage['Butter']。 注意 为了增加PROC OPTMODEL程序的可读性,在建模时有 如下约定: ·建议在使用PROC OPTMODEL声明索引集名称和使用索引集时, 将索引集名称全部大写,如PRODUCT、RESOURCE。 ·决策变量名称、隐式变量名称和约束条件名称的首字母大写。 ·虚拟参数小写。 21.3.5 求解器的调用 运用SOLVE语句可以调用PROC OPTMODEL中的求解器,基本语 法如下: SOLVE [OBJECTIVE|OBJ 目标函数名称] [WITH 求解器类别] [/SOLVER=求解器关键字]; 这里关键字OBJECTIVE或者OBJ用来指定目标函数名称,如果不指 定目标函数名称,系统默认将最近声明的目标函数作为当前问题的优化 目标。 关键字WITH用来指定求解器类别,在PROC OPTMODEL中可以使 用的求解器类别如下。 ·LP:默认用来求解线性规划问题,默认算法为对偶单纯形法。 ·MILP:默认用来求解混合整数线性规划问题,默认算法为Branch and Cut方法。 ·QP:默认用来求解二次线性规划问题,默认算法为内点法。 ·NLP:默认用来求解一般的非线性规划问题,默认算法为内点 法。 如果不使用关键字WITH来指定求解器类别,系统将根据目标函 数、决策变量和约束条件自动判别优化问题的类型,选择默认的求解器 类别。 对于各类优化问题,选项SOLVER=可以用来指定算法,用来求解 LP问题的算法有单纯形法、对偶单纯形法、内点法和网络单纯形法,对 应的求解器关键字为PS、DS、IP和NS。有兴趣的读者可以参考SAS帮 助文档查看用于求解其他问题的算法。 21.3.6 数据输出 PROC OPTMODEL中提供了两种语句用于输出带有格式的数据: PRINT语句和PUT语句。 1.PRINT语句 PRINT语句以表格的形式在“结果”窗口输出带有格式的字符型数据 或数值型数据。以下代码展示了PRINT语句常见的几种用法: proc optmodel; num x = 4.3; var y{j in 1..4} init j*3.68; print y; print (x * .265) dollar6.2; /* (expression) [format] */ print {i in 2..4} y; /*{index-set} identifier-expression */ print {i in 1..3}(i + i*.2345692) best7.;/* {index-set} (expression) [format] */ print "Line 1"; /* string */ quit; 其中: ·第一个PRINT语句输出了变量y的初始值,因为y是一个数组,所 以默认输出了数组中的所有值,如图21.4所示。 图21.4 PRINT语句输出数组 ·第二个PRINT语句输出带格式的数据,因为输出内容是一个表达 式,必须用()将表达式括起来。第二个PRINT语句的输出如图21.5所 示。 图21.5 PRINT语句输出带格式的表达式结果 ·第三个PRINT语句输出由索引集控制的数组数据,声明变量y时索 引集为1..4,但是PRINT语句使用了{i in 2..4},故只输出索引集中项为2 到4的变量值。输出结果如图21.6所示。 图21.6 PRINT语句输出由索引集控制的数组数据 ·第四个PRINT语句输出了由索引集控制的表达式结果,在代码中 表达式必须用()括起来,并在表达式后面指定了输出数据的格式。第 五个PRINT语句输出了一个字符串。输出结果如图21.7所示。 图21.7 2.PUT语句 PRINT语句输出由索引集控制的表达式和字符串 PROC OPTMODEL中的PUT语句在语法上和DATA步中类似,可以 将带有格式的优化结果输出到日志或者其他外部文件中。在PROC OPTMODEL中,PUT语句最常见的用途是调试程序或者快速检查数 据。以下代码展示了PUT语句的几种常见用法: proc optmodel; number a=1.7, b=2.8; set s={a,b}; put a b; put a= b=; put 'Value A: ' a 8.1 @30'Value B: ' b 8.; string str='Ratio (A/B) is:'; put str (a/b); put s=; quit; PUT语句的结果输出在日志中,日志的截图如图21.8所示。 图21.8 使用PUT语句快速查看数据示例 ·第一个PUT语句直接输出了a和b的值,数字的默认格式为 BEST12.,数字之间用空格隔开,每个数字的前面或后面都没有多余的 空格。 ·第二个PUT语句使用了=指定在输出数据的同时输出参数(或者变 量)的名称。 ·第三个PUT语句演示了带格式的输出,@指定了固定的列,8.1和8. 指定了输出数字的格式,并且格式8.使得参数b的取值2.8四舍五入为3。 PUT语句的这种用法主要用于制作报表。 ·第四个PUT语句用于输出字符串和表达式结果。 ·最后一个PUT语句用于输出一个集合和集合的名称。 注意 PRINT语句不可以输出集合;PUT语句可以输出集合。 例21.3:将例21.1中的线性规划问题用索引集的方式进行决策变 量、目标函数、约束条件等声明,并用EXPAND语句将变量目标函数和 约束条件展示出来,然后再求解。 示例代码如下: proc optmodel; /*declare sets and parameters*/ set PRODUCT=/croissant toast baguette cookie/; num Selling_Price{PRODUCT}=[440330315385]; set RESOURCE=/flour cream butter/; num Cost{RESOURCE}=[204035]; num Available{RESOURCE}=[1306040]; num Requirement{PRODUCT,RESOURCE}=[3 2 2 3 1.5 1 4 0.5 1 3.5 2 1]; /*declare variables*/ var X{PRODUCT}>=0; /*decision variables*/ impvar Amount_Used{r in RESOURCE}=sum{p in PRODUCT}Requirement[p,r]*X[p]; /*implicit variables*/ impvar Revenue=sum{p in PRODUCT}Selling_Price[p]*X[p]; impvar Costing=sum{r in RESOURCE}Cost[r]*Amount_Used[r]; /*declare constraints*/ con Usage{r in RESOURCE}: Amount_Used[r]<=Available[r]; /*declare objective*/ max Profit=Revenue-Costing; expand / var impvar; expand Profit; expand Usage; /*call Solver*/ solve obj Profit with lp/solver=ds; /*print Solution*/ print {p in PRODUCT: X[p]>0} X; quit; 代码中的内容已经分别在前面介绍相关声明时解释过了,这里不赘 述。需要指出的是,在PRINT语句的索引集中,使用了“:X[p]>0”对索 引集PRODUCT进行筛选,只输出决策变量组中X[p]>0时X的取值。 接下来,查看输出结果,第一部分内容是EXPAND语句输出的决策 变量和隐式变量的表达式、目标函数和约束条件,如图21.9所示。在隐 式变量Amount_Use、Revenue和Costing的表达式中,可以观察到参数已 经分别用数字替代了,但是隐式变量Amount_Used[flour]、 Amount_Used[cream]和Amount_Used[butter]仍然保留在表达式中。 图21.9 例21.3EXAPND语句输出结果 求解的结果输出如图21.10所示。和例21.1中输出的最优目标值相 等,并且这里只输出了产量大于0的baguetter、cookie和toast的产量。 图21.10 例21.3输出优化结果 3.后缀(SUFFIXES) 在PROC OPTMODEL中,若在标识表达式后面使用后缀,可以用 来取得或者修改求解器中的一部分设置和结果。使用语法如下: 标识表达式.后缀名称 后缀主要用在以下3种途径中: ·用在PRINT语句和PUT语句中进行程序调试和结果展示。 ·用在各式各样的表达式中参与计算。 ·通过CREATE DATA语句输出到数据集中。 在PROC OPTMODEL中,决策变量、隐式变量、约束条件、目标 函数等不同对象可使用的后缀是不一样的,例如,不可以使用后 缀.INIT对一个目标函数赋初值,虚拟参数后面不可以使用任何后缀。 表21.4中包含了不同对象可以使用的后缀名称及其作用描述,并指 出了哪些后缀的数据可以在PROC OPTMODEL中进行修改。 表21.4 后缀列表 目前,只有在LP求解器中可以使用.STATUS后缀修改决策变量或约 束条件的状态信息。一般情况下,约束条件的BODY指的是约束条件左 边表达式的取值,但是在PROC OPTMODEL中并不限制约束条件的表 达方式,约束条件可以表示为f(x)(≤,=,≥)g(x),此时约束条 件的BODY指的是f(x)-g(x)。例如,约束条件x-4≥2x-y-2的BODY 和约束条件x-(2x-y)≥-2+4的BODY是一样的,为-x+y,如果最优解为 x=1,y=2,则约束条件.BODY的取值为1。 在例21.1中,可以使用.BODY和.DUAL查看原料flour、cream和 butter的使用量及影子价格。代码如下: print flour.body cream.body butter.body; print flour.dual cream.dual butter.dual; 输出如图21.11所示。 图21.11 约束条件.BODY和.DUAL示例 约束条件flour.body、cream.body和butter.body的取值分别为130、60 和40,和各自的右端项的取值相等,表明在最优解下这3种原料都正好 全部用完。这3种原料的影子价格分别为27.5、22.5和58.75,表示这3种 原料每增加一个单位的供应量,所能够带来的利润提升量。 21.4 读取SAS数据集 在前面的介绍中,集合、参数和参数数组的取值都是在PROC OPTMODEL中进行声明时直接赋值的。这一节将介绍如何使用READ DATA语句对集合、参数和参数数组赋值。 可使用READ DATA语句通过数据集对集合、参数和参数数组进行 赋值,基本语法如下: READ DATA 数据集[NOMISS] INTO [集合名称=][KEY变量1[KEY变量2 ...]][读取变量语句1][...读取变量语句n]; 其中: ·数据集表示要从中读取数据的数据集。 ·[KEY变量1[KEY变量2...]]中的KEY变量1、KEY变量2都是来自数 据集的变量,称为KEY变量。KEY变量可以是一个变量,也可以由多个 变量共同组成,但是KEY变量的值在数据集中的必须是唯一的。如果在 READ DATA语句中指定了集合名称(集合必须已经声明过),KEY变 量中的值将保存在集合中,成为集合的项。在READ DATA语句中, KEY变量有两个作用:一是为[集合名称=]指定的集合赋值,二是在为 后面“读取变量语句”中指定的参数数组赋值时提供标识。如果KEY变量 是由多个变量共同组成的,那么集合中的项也必须是和多个变量对应的 多元组。 ·如果在读取数据集时指定了KEY变量,则READ DATA语句会读取 数据集中的所有观测;如果不指定KEY变量,那么READ DATA语句只 读取数据集中的第一条观测。如果PROC OPTMODEL发现观测在KEY 变量上不唯一,将会在日志中输出警告,并且后面读入的数据将会覆盖 前面读入的数据。 ·读取变量语句的作用是将数据集中变量的值赋给参数或参数数 组,选项NOMISS表示在给数组赋值时,如果遇到数据集中有缺失值, 则不将缺失值赋给数组,数组的取值在标识为KEY变量当前值时保持原 值。一般读取变量的语句有如下表达形式: 标识表达式=数据集变量名称 [/TRIM选项] 等号左边是标识表达式(由参数或者参数数组名称和数组的标识组 成),右边是数据集变量名称,该语句表示将数据集中变量的取值赋给 左边的数组。标识表达式的标识部分往往可以省略,从而只包含数组名 称,至于标识,则由KEY变量指定的数据集中变量的值决定。 每读入数据集的一条观测时,PROC OPTMODEL都会首先获取 KEY变量指定的数据集的变量在当前观测上的值;如果语句中通过[集 合名称=]指定了需要赋值的集合,那么KEY变量指定的变量在当前观测 的值会作为该集合的一个项,添加到集合中;接下来,PROC OPTMODEL会把语句“标识表达式=数据集变量名称[/TRIM选项]”中“数 据集变量名称”指定的数据集变量在当前观测的值赋给“标识表达式”指 定的参数或参数数组,如果是参数数组,如前所述,需要赋值的数组元 素的标识由KEY变量指定的变量在当前观测上的值确定。 在进行赋值的时候,等于号左边的标识表达式和右边数据集中的变 量名称没有任何关系,如果两个参数和变量的名称完全不一样,同样可 以进行赋值。 如果数据集中的变量名称和参数或参数组的名称相同,则可以省略 等号和等号右边的部分,直接表示为: 标识表达式 [/TRIM选项] 有时,在读取变量语句中,数据集中的变量名称也可以通过 COL(表达式)计算得出,此时读取该变量的语句可以表示为: 标识表达式=COL(表达式)[/TRIM选项] TRIM选项用来指定在读取字符数据时,对字符数据前后空格的处 理方法。以下是4种不同的TRIM选项。 ·TRIM|TR:删除字符数据前后的空格,这是默认选项。 ·LTRIM|LT:删除字符数据前面的空格。 ·RTRIM|RT:删除字符数据后面的空格。 ·NOTRIM|NT:保留原始字符数据,不删除任何前后空格。 下面将介绍几个读取数据集的例子。代码如下: /*Example 1*/ data work.indata; input j k; datalines; 1 2 ; proc optmodel; num j,k; read data work.indata into j k; put j= k=; quit; 上面的程序用READ DATA语句从数据集work.indata中将变量j和k 的值复制给了PROC OPTMODEL中的参数j和k。日志中的输出为: j=1k=2。 /*Example 2*/ data work.invdata; input item $ invcount; datalines; table 100 sofa 250 chair 80 ; proc optmodel; set<string> ITEMS; number Invcount{ITEMS}; read data work.invdata into ITEMS=[item] Invcount; print invcount; quit; 在上面程序中,READ DATA语句从数据集work.invdata中读取了两 个变量的值。数据集中的变量item填充了集合ITEMS,同时,参数数组 Invcount也被赋予对应观测中变量Invcount的值。在赋值的时候,参数的 数据类型必须和数据集中变量的数据类型一致。输出如图21.12所示。 图21.12 Example 2输出内容 /*Example 3*/ data work.exdata; input column1 column2; datalines; 1 2 3 4 ; proc optmodel; number n init 2; set <num> INDX; number p{INDX }, q{INDX }; read data work.exdata into INDX =[_N_] p=column1 q=col("column"||n); print p q; quit; 上面程序中的READ DATA语句通过数据集work.exdata为集合INDX 和参数数组p、q进行赋值。这里使用了COL(表达式)计算出了数据集 变量名称(如,col(“column”||n)),并且标识表达式只指定了参数数 组名称(p和q),而没有指定标识,那么系统将默认使用数据集中的 KEY变量(_N_)作为该参数数组的标识。这段READ DATA语句和下 面的代码是等价的。 read data work.exdata into INDX =[_N_] p[_N_]=column1 q[_N_]=col("column"||n); 输出如图21.13所示。 图21.13 Example 3输出内容 还有一种使用索引集读取数据的形式,如下: {索引集} <读取变量语句> 这里的读取变量语句必须用<>括起来。在索引集中可以声明虚拟参 数,并应用在COL表达式中。对于这种形式,在读取数据时,将根据索 引集中的项依次执行读取变量语句,直到遍历完索引集中所有的项为 止。下面一段代码展示了这种形式的使用方法和数据集特点。 /*Example 4*/ data work.dmnd; input loc $ day1 day2 day3 day4 day5; datalines; East 1.1 2.3 1.3 3.6 4.7 West 7.0 2.1 6.1 5.8 3.2 ; proc optmodel; set DOW = 1..5; /* days of week, 1=Monday, 5=Friday */ set<string> LOCS; /* locations */ number demand{LOCS, DOW}; read data work.dmnd into LOCS=[loc] {d in DOW} <demand[loc, d]=col("day"||d) >; print demand; quit; 上面程序在声明集合LOCS时,没有给LOCS赋初始值,因此必须指 定集合中的项的数据类型;如果不指定数据类型,PROC OPTMODEL 默认数据类型为NUM。{d in DOW}指定了一个索引集,当虚拟参数d在 DOW中遍历时,变量day1到day5的值依次被赋给了参数demand[loc,1] 到demand[loc,5]。这段READ DATA语句和下面的READ DATA语句是 等价的。 read data work.dmnd into LOCS=[loc] demand[loc,1]=day1 demand[loc,2]=day2 demand[loc,3]=day3 demand[loc,4]=day4 demand[loc,5]=day5; 输出如图21.14所示。 图21.14 Example 4输出内容 例21.4:在PROC OPTMODEL中,将数据集sashelp.zipcode中的纬 度数据(Y)、经度数据(X)及城市名称(CITY)数据读入参数数组 Latitude、Longtitude和City中。Sashelp.zipcode的部分数据如表21.5所 示。 表21.5 Sashelp.zipcode中的部分数据 示例代码如下: proc optmodel; /*declare sets and parameters*/ set <num> ZIPCODE; num Latitude{ZIPCODE},Longtitude{ZIPCODE}; str City{ZIPCODE}; /*read data from SAS data sets*/ read data sashelp.zipcode(obs=5) into ZIPCODE=[zip] Longtitude=x Latitude=y City; put ZIPCODE=; print Longtitude Latitude City; quit; 这个例子中首先声明了集合ZIPCODE,然后,以集合ZIPCODE为 索引集声明了两个数值型参数数组Latitude和Longtitude,以及一个字符 型参数数组City。 接下来使用READ DATA语句从数据集sashelp.zipcode中读取数据, 将数据集中的变量zip赋值给集合ZIPCODE,并将对应观测的变量x、变 量y和City分别赋值给数组Longtitude、Latitude和City。由于City在数据 集中的变量名称和参数数组中相同,所以在对参数数组赋值时,不需要 使用=来指定数据集中变量的名称。注意到,数据集sashelp.zipcode中存 在更多的变量,但是由于PROC OPTMODEL中不需要这些变量的数 据,因此,不用在READ DATA语句中将它们读取进来。 数据集选项OBS=使得READ DATA语句只从数据集中读取了前5条 数据,其他可以在READ DATA语句或者CREATE DATA语句(稍后将 在21.5中介绍)中使用的数据集选项如下。 ·选项FIRSTOBS=n:和DATA步中的用法一样,表示跳过前面n-1条 观测,从第n条观测开始读取。 ·选项PW=:操作被保护的数据集时,用来指定密码。 ·选项READ=:操作被保护的数据集时,用来指定密码。 ·选项RENAME=:修改数据集中的变量名称。 ·选项WHERE=:在读取或者输出数据集时,只读取或只输出符合 固定条件的观测。 代码的最后用PUT语句在日志中输出了集合ZIPCODE,用PRINT语 句输出了参数数组,这样可以快速检查数据读入是否正确。日志和输出 结果分别如图21.15和图21.16所示。日志中显示系统只读取了5条观测, PUT语句输出了集合ZIPCODE中的项。 图21.15 例21.4的日志内容 例21.5:将例21.1中的原料和产品用料数据存储在了数据集 work.resouce_data和work.product_data中,在PROC OPTMODEL中运用 READ DATA语句从数据集中读取数据并赋给集合PRODUCT、 RESOURCE、参数数组Requirement、Cost和Available。代码如下: data work.resource_data; input Res $ cost Amount; datalines; flour 20 130 cream 40 60 butter 35 40 ; run; data work.product_data; length prod $9; input prod $ Selling_Price flour cream butter; datalines; croissant 440 3 2 2 toast 330 3 1.5 1 baguette 315 4 0.5 1 cookie 385 3.5 2 1 ; run; proc optmodel; /*declare sets and parameters*/ set <str> PRODUCT, RESOURCE; num Cost{RESOURCE},Available{RESOURCE}; num Selling_Price{PRODUCT}; num Requirement{PRODUCT,RESOURCE}; /*read data from SAS data sets*/ read data work.resource_data into RESOURCE=[Res] Cost Available=Amount; read data work.product_data into PRODUCT=[prod] Selling_Price {r in RESOURCE} <Requirement[prod,r]=col(r)>; print Cost dollar. Available; print Selling_Price dollar. Requirement; quit; 这里的语句“read data work.resource_data into RESOURCE=[Res]Cost Available=Amount;”中标识表达式中只含有参数数组的名称,而省略了 标识,默认情况下,系统使用数据集中KEY变量(Res)作为参数数组 的标识。这段代码和下面的代码是等价的。 read data work.resource_data into RESOURCE=[Res] Cost[Res]=Cost Available[Res]=Amount; 图21.16 例21.4输出内容 图21.17 例21.5输出内容 输出如图21.17所示。 例21.6:(多个KEY变量的情形)数据集work.inventory中保存了某 仓库按车型、按颜色分的库存数据,运用PROC OPTMODEL将库存变 量init_inv的数据读入参数数组Initinv中。 以下代码生成了数据集work.inventory。 data work.inventory; input package $ color $ inventory; datalines; Excelle Yellow 20 Excelle Grey 30 Excelle Black 40 Excelle Blue 10 Malibu Red 30 Malibu White 30 Malibu Grey 20 ; run; 以下代码用PROC OPTMODEL将work.inventory中的数据读入集合 PACKAGE_COLOR和参数数组Initinv中。 proc optmodel; /*declare sets and parameters*/ set <str,str> PACKAGE_COLOR; num Initinv{PACKAGE_COLOR}; /*read data from SAS data sets*/ read data work.inventory into PACKAGE_COLOR=[pkg colr] Initinv=inventory; put PACKAGE_COLOR=; print Initinv; quit; 这段代码中声明了集合PACKAGE_COLOR,其中每个项都是一个 二元组;然后以该集合为索引集定义了一个参数数组Initin。数据集 work.inventory中的变量pkg和colr共同组成了KEY变量,READ DATA语 句将这两个变量的值赋给了集合PACKAGE_COLOR,相应观测的 inventory的取值则赋给了数组Initinv。PUT语句在日志中输出了集合 PACKAGE_COLOR,如图21.18所示。PRINT语句输出了参数Initinv的 数据,如图21.19所示。 图21.18 图21.19 集合PACKAGE_COLOR 例21.6输出参数Initinv的内容 21.5 创建SAS数据集 和READ DATA语句的语法类似,使用CREATE DATA语句创建数 据集的基本语法如下: CREATE DATA 数据集FROM [KEY变量1 [KEY变量2...]][=KEY集合][输出变量语句1][...输出变量语句n]; 其中: ·数据集表示要创建的数据集名称。 ·KEY集合是一个集。[KEY变量1[KEY变量2...]][=KEY集合]表示将 KEY集合中的项输出到KEY变量中,KEY变量1和KEY变量2等都是输 出数据集中新创建的变量名称。如果集合中的项是多元组,那么多元组 每个位置的数据将依次赋给对应的KEY变量。 ·输出变量语句的作用是将PROC OPTMODEL中的数据输出到数据 集的变量中,一般有如下表示形式: 变量名称=表达式[/COLUMN选项] 等号左边表示数据集中新创建的变量名称,等号右边是PROC OPTMODEL中的变量、参数或常数等组成的表达式,该语句的作用是 将右边表达式的值赋给左边数据集中的变量。 如果数据集中的变量名称和右边表达式的名称相同,则可以省略等 号和等号左边的部分,直接表示为: 标识表达式[/COLUMN选项] 有时,左边数据集中的变量名称也可以通过COL(表达式)计算得 到,那么输出变量语句可以表示为: COL表达式=表达式[/COLUMN选项] 注意 如果等号右边的表达式中包含运算符(如+、-、*、/等) 或者函数,则必须将表达式用()括起来,否则系统会报错。 COLUMN选项主要有以下几种。 ·FORMAT=:指定当前变量的输出格式,如8.2、DOLLAR.、 COMMA8.2、DATE9.。 ·INFORMAT=:指定当前变量的输入格式。 ·LABEL=:指定当前变量的标签,标签的内容可以是用引号引起来 的字符串或者是用括号括起来的表达式。 ·LENGTH=:指定当前变量的长度,字符型变量的长度为1到32767 个字符。 下面一段代码演示了如何运用CREATE DATA语句生成数据集 work.squares,其中包含了两个变量sequence和square,sequence由集合 INDX赋值,square由以INDX为索引集的数组sq赋值。变量sequence为 KEY变量,输出格式为hex2.,长度为3;变量square的输出格式为6.2。 proc optmodel; set <num> INDX=1..10; num Sq{i in INDX} = i*i; create data work.squares from [Sequence=INDX/format=hex2./length=3] square= Sq/format=6.2; run; proc print data=work.squares; run; PRINT过程输出如图21.20所示。 图21.20 PRINT过程输出内容 和READ DATA语句中类似,CREATE DATA语句也可以使用索引 集来输出变量,语法如下: {索引集} <输出变量语句> 这里的输出变量语句必须用<>括起来,索引集中可以声明虚拟参 数,并应用在COL表达式。运用这种形式输出变量时,将根据索引集中 的项依次执行输出变量语句,直到遍历完索引集中的所有项为止。下面 一段代码展示了这种形式的使用方法。 proc optmodel; set <string> alph = {'a', 'b', 'c'}; var x{1..3, alph} init 2; create data work.example from [i]=(1..3) {j in alph}<col("x"||j)=x[i,j]>; quit; proc print; run; 上面的CREATE DATA语句等价于下面代码: create data work.example from [i]={1..3} xa=x[i,a] xb=x[i,b] xc=x[i,c]; 输出如图21.21所示。 图21.21 使用索引集输出数据集 例21.7:将例21.5中的优化结果和原材料利用率数据输出到数据集 work.opt_solution和work.resource_usage中。 示例代码如下: proc optmodel; /*declare sets and parameters*/ set <str> PRODUCT, RESOURCE; num Cost{RESOURCE},Available{RESOURCE}; num Selling_Price{PRODUCT}; num Requirement{PRODUCT,RESOURCE}; /*read data from SAS data sets*/ read data work.resource_data into RESOURCE=[Res] Cost Available=Amount; read data work.product_data into PRODUCT=[prod] Selling_Price {r in RESOURCE} <Requirement[prod,r]=col(r)>; /*declare variables*/ var X{PRODUCT}>=0; /*decision variables*/ impvar Amount_Used{r in RESOURCE}=sum{p in PRODUCT}Requirement[p,r]*X[p]; /*implicit variables*/ impvar Revenue=sum{p in PRODUCT}Selling_Price[p]*X[p]; impvar Costing=sum{r in RESOURCE}Cost[r]*Amount_Used[r]; /*declare constraints*/ con Usage{r in RESOURCE}: Amount_Used[r]<=Available[r]; /*declare objective*/ max Profit=Revenue-Costing; /*call Solver*/ solve obj Profit with lp/solver=ds; /*create data sets*/ create data work.opt_solution from [Products]={p in PRODUCT: X[p]>0} Volume_Produced=X Profit= (Selling_Price[p]*X[p] - sum{r in RESOURCE} Cost[r]*Requirement[p,r]*X[p])/format=comma8.; create data work.resource_usage from [Resources]={r in RESOURCE} Amount_Used[r] TotalCost=(Cost[r]*Amount_Used[r])/format=comma8. Usage= (Amount_Used[r]/Available[r]) /format=percent10.2; quit; proc print data= work.opt_solution; run; proc print data= work.resource_usage; run; 在上面程序中,第一个CREATE DATA语句创建了数据集 work.opt_solution,并使用表达式(Selling_Price[p]*X[p]-sum{r in RESOURCE}Cost[r]*Requirement[p,r]*X[p])计算出了每种产品的利 润,然后输出给变量profit。创建的数据集如图21.22所示。 图21.22 例21.7PRINT过程输出内容 21.6 本章小结 本章在开始部分介绍了PROC OPTMODEL中涉及的基本概念,如 参数、索引集、数据类型、表达式等概念。然后通过示例介绍了运用 PROC OPTMODEL建立模型和求解模型的基本结构。接下来,结合大 量示例详细介绍了PROC OPTMODEL中各种要素的声明和使用方法; 最后,介绍了如何运用READ DATA语句和CREATE DATA语句在 PROC OPTMODEL中读取和创建数据集。 第22章 PROC OPTMODEL程序设计 在前面关于PROC OPTMODEL的介绍中,讲解了PROC OPTMODEL的基本结构,以及如何运用PROC OPTMODEL来求解一些 相对简单的线性规划问题。在这些问题中,大多数没有涉及数组及索引 集的加工与处理。然而,在实际应用中,优化问题都比较复杂,离不开 这些加工与处理,并且在很多情况下,条件控制与循环的使用可以使代 码更加有效率、简洁。在PROC OPTMODEL中,如何实现常见的流程 控制方法(如条件控制与循环)以及加工索引集是本章要讨论的问题之 一。通常情况下,模型建立之后,决策变量的增加或减少、参数的改 变、约束条件以及目标函数的变化都会带来模型更新的问题。在PROC OPTMODEL中,如何实现对优化模型的更新是本章要讨论的另外一个 问题。本章的最后一部分将介绍一类特殊的线性规划问题模型——网络 流模型。 22.1 PROC OPTMODEL中的流程控制方法与集合运 算 常见的流程控制方法有条件语句和循环语句。在下文的介绍中,读 者可以了解到如何在PROC OPTMODEL实现这些常见的流程控制。此 外,对索引集的灵活操作也是PROC OPTMODEL的特色之一。此操作 离不开集合运算符,PROC OPTMODEL提供多种集合运算符(包括常 见的集合运算符,如交集、并集)以及集合处理函数供用户使用。 22.1.1 常见的流程控制方法 在PROC OPTMODEL中,常见的流程控制语句如下: ·DO(界定语句组) ·DO(循环) ·DO UNTIL ·DO WHILE ·FOR ·IF THEN/IF THEN ELSE ·LEAVE ·STOP 下面将一一介绍这些流程控制语句的功能。 (1)DO(界定语句组) 在PROC OPTMODEL中,关键字DO可以用来界定一个语句组,语 法如下: DO; 语句1; 语句2; … 语句N; END; 一对DO-END语句界定了一个语句组。DO-END常常配合循环语句 以及条件语句使用。例如,当条件语句的逻辑表达式为真时,执行某个 语句组(依次执行该语句组内的各个语句)。 (2)DO(循环) 关键字DO还可以表示循环,具体的语法如下: DO参数=值1 [,值2,..., 值K]; 语句1; 语句2; … 语句N; END; 在执行时,PROC OPTMODEL将语句中的参数赋值为“值1”并依次 执行“语句1”到“语句N”;接着,参数将被赋值为“值2”,并依次实行“语 句1”到“语句N”;重复上述过程,直至遍历指定参数所有可能的取值为 止。上述语法中的“值1”、“值2”等参数的取值既可以是数值型也可以是 字符型,还可以是某个特定的集合,甚至可以包含WHILE或者UNTIL 等关键字。这里需要注意的是,DO语句中参数的类型必需提前声明。 例如,以下代码定义了参数i的类型,并指定i的取值为1、3、5。 proc optmodel; number i; do i =1, 3, 5; put i; end; quit; 运行上述代码,在日志中,输出结果为1、3和5。可以将上述所有i 的值定义为一个集合,记为S。指定i=S,那么i会自动遍历整个集合S内 的所有元素。例如,以下代码的输出结果与前一段代码一致。 proc optmodel; number i; set S = {1, 3, 5}; do i = S; put i; end; quit; DO语句中参数的取值还可以结合UNTIL语句或WHILE语句使用。 例如,假设在上面的代码中,i的取值为S中满足条件的部分元素,即不 等于5,那么在这种情况下,可以使用WHILE语句加以判断。具体代码 如下: proc optmodel; number i; set S = {1, 3, 5}; do i = S while (i ne 5); put i; end; quit; 运行上面的代码,在日志中输出结果为:1和3。 如果参数的取值是按照一定步长变化的,还可以通过指定参数取值 边界并结合BY语句控制步长,来实现对参数取值的控制。具体如下面 的代码: proc optmdeol; number i; do i = 1 to 5 by 2; put i; end; quit; 运行上面的代码,在日志中输出结果为:1、3和5。 在前面的例子中,DO语句中参数的取值都为数值型。下面的例 子,定义了一个字符型参数origin。在DO语句中,origin遍历了索引集 FROM中的所有项。代码如下: proc optmodel; set FROM = {'NYC', 'NJ', 'Boston'}; string origin; do origin = FROM; put origin; end; quit; 运行上面的代码,在日志中输出结果为:NYC、NJ和BOSTON。 (3)DO UNTIL DO UNTIL可以用来循环执行某些操作,直至某一条件成立才退出 循环。其语法如下: DO UNTIL(逻辑表达式); 语句1; 语句 2; … 语句N; END; 每次执行完“语句1”到“语句N”,DO UNTIL语句都会判断逻辑表达 式是否为真(逻辑表达式的值非零或者为非缺失值表示为真)。若表达 式为真则退出循环,否则继续执行“语句1”到“语句N”。例如,以下代码 的输出结果为1和2。 proc optmodel; number i ; i =1; do until(i=3); put i; i = i+1; end; quit; 在DO UNTIL的语法中,逻辑表达式也可以是带逻辑运算符的多重 逻辑表达式。常见的逻辑运算符有:OR(或)、AND(且)、 NOT(否定)、>(大于)、>=(大于或者等于)、<(小于)、 <=(小于或者等于)、IN(属于)、NOT IN(不属于)、 WITHIN(包含)、NOT WITHIN(不包含)。 例如,以下代码中,逻辑表达式是由关键词AND连接的双重逻辑表 达式。在初始阶段,i被赋值为1,执行第一次循环,输出当前i值(为 1),并将i值自增1个单位(为2)。紧接着,判断DO UNTIL中的逻辑 表达式,以确定是否执行第二次循环,此时,发现逻辑表达式为真,于 是终止循环。因此,代码的输出结果为1。 proc optmodel; number i ; i =1; set A = {2, 5, 7}; do until(i<3 and i IN A); put i; i = i+1; end; quit; (4)DO WHILE DO WHILE也是一个循环语句。前面介绍的DO UNTINL语句是先 执行语句(或语句组)再判断逻辑表达式,而DO WHILE语句则是先判 断逻辑表达式,当逻辑表达式为真时,才执行相应的语句(或语句 组),若逻辑表达式不为真,则退出循环。因此,DO UNTIL语句至少 执行一次语句(或语句组),而DO WHILE语句则有可能不执行任何语 句(或语句组)。DO WHILE语句的语法如下: DO WHILE(逻辑表达式); 语句1; 语句 2; … 语句N; END; 例如,在下面的代码中,i的初始值为1,小于3,逻辑表达式成 立,PROC OPTMODEL在日志中输出当前i值(为1),并将i值增加一 个单位为2;接着,重新判断逻辑表达式,仍然为真,继续在日志中输 出当前i值(为2),并将i值增加到3;此时,逻辑表达式不成立,终止 循环。因此,整段的代码的输出为1和2。 proc optmodel; number i ; i =1; do while(i<3); put i; i = i+1; end; quit; (5)FOR 在PROC OPTMODEL中,FOR语句可以用来对某个或者某几个索 引集里的项执行相同的语句,其语法如下: FOR{索引集}语句; 例如,在下面的代码中,FOR语句共定义了两个索引集:{1,2}以 及{'a','b'},对于每一组索引集,PROC OPTMODEL都执行相同的语 句,即在日志中输出当前的i值与j值。 proc optmodel; for {i in 1..2, j in {'a', 'b'}} put i = j =; quit; 运行上述代码,得到的输出结果如下: i i i i =1 =1 =2 =2 j=a j=b j=a j=b (6)IF THEN 在PROC OPTMODEL中,可以通过IF THEN语句有条件地执行一些 语句,从而实现对程序流程的控制,其语法如下: 当逻辑表达式为真时,执行语句1。进一步,如果存在ELSE语句, 那么当逻辑表达式不为真时,执行语句2。上述语法中,语句1与语句2 均可以是语句组。 下面代码使用FOR语句来遍历索引集ORI中的所有项,对每一个项 都判断其是否在出发地FROM里,如果当前项在出发地FROM里,那么 在日志中输出“项is in the FROM”,否则,在日志中输出“项is not in the FROM”。 proc optmodel; set<string> FROM = /'北京', '石家庄'/; set<string> START = / '北京', '天津'/; for {s in START} do; if s in FROM then put s "is in the FROM"; else put s"is not in the From"; end; quit; 运行上述代码,输出结果为:北京is in the FROM、天津is not in the FROM。 (7)LEAVE LEAVE语句常常配合DO(循环)、DO UNTIL、DO WHILE以及 FOR语句使用,用来终止执行当前包含LEAVE的循环体,其他未直接 包含LEAVE语句的循环体则不受影响,下一次循环也不受影响。例 如,以下代码中,共定义了两个循环体,一个是参数i定义的外循环, 另外一个是参数j定义的内循环,如图22.1所示。 图22.1 LEAVE语句代码示例 图22.1所示的代码在SAS/OR 13.1中运行时,当i=1时,进入内循 环,条件语句不成立,内循环不执行任何操作,j值不断自增到4,退出 内循环,输出i值和j值,分别为1和4;接着,i值增加到2,再次进入内 循环,当j=2时,条件语句成立,执行LEAVE语句,退出j定义的内循 环,输出i值和j值,分别为2和2;最后,i值增加到3,再次进入内循 环,条件语句不成立,j值不断自增到4,退出内循环,输出当前i值与j 值,分别为3和4。 整段代码的输出结果如下: i=1 j=4 i=2 j=2 i=3 j=4 (8)STOP STOP语句用于终止所有包含STOP的语句,包括条件语句和循环语 句(可以是多重循环语句)。例如,以下代码(如图22.2所示)在 SAS/OR 13.1中运行时,当i=1时,进入j定义的内循环,条件语句不成 立,j值不断自增到4,退出j定义的内循环,输出当前i值和j值,分别为1 和4;接着,i自增为2,当j=2时,条件语句成立,执行STOP语句,退出 所有包含STOP语句的内循环和外循环。 图22.2 STOP语句代码示例 因此,整个代码的输出如下: i=1 j=4 22.1.2 常见的集合运算处理 数学上,我们称集合内元素的个数为该集合的基数。由于索引集本 身是一个集合,因此这里沿用这一术语。在使用PROC OPTMODEL进 行建模的过程中,无论是数组的加工,还是约束以及目标函数的表达, 往往都伴随着索引集的处理。常见的用于处理索引集的集合表达式以及 函数如下: ·AND表达式 ·OR表达式 ·CARD函数 ·CORSS表达式 ·DIFF表达式 ·INTER表达式 ·IF THEN ELSE表达式 ·IN|NOTIN表达式 ·MAX|MIN集成表达式 ·PROD集成表达式 ·SLICE表达式 ·SYSDIFF表达式 ·SETOF集成表达式 下面一一介绍这些集合表达式,以及它们的含义和用法。 (1)AND表达式 在PROC OPTMODEL中,AND表达式的作用是判断索引集中所有 的项是否都满足逻辑表达式。其语法如下: AND {索引集} 逻辑表达式 AND表达式会依次判断索引集中的项是否满足逻辑表达式。若当前 某个项不满足逻辑表达式,那么AND表达式停止判断其余索引集,直接 返回0值(假);反之,如果所有的项都满足逻辑表达式,那么,AND 表达式返回值为1(真)。例如,以下代码中,第一个PUT语句的输出 结果为1;第二个PUT语句的输出结果为0。 proc optmodel; put (and{i in 1..5} i<10); *first put statement; put (and{i in 1..5} i NE 3); *second put statement; quit; (2)OR表达式 在PROC OPTMODEL中,OR表达式的语法如下: OR {索引集} 逻辑表达式 OR表达式的作用是判断索引集中是否存在使得逻辑表达式成立的 项,如果存在某个项使得逻辑表达式成立,那么OR表达式的返回值为 1;否则,表达式的返回值为0。例如,下面代码中,第一个PUT语句的 输出结果为1,第二个PUT语句的输出结果为0。 proc optmodel; put (or{i in 1..5} i<3); *first put statement; put (or{i in 1..5} i>6); *second put statement; quit; (3)CARD函数 在PROC OPTMODEL中,CARD函数用于返回某个集合(可以是集 合表达式运算的结果)的基数。CARD函数的语法如下: CARD(集合表达式) 例如,以下代码中,由于集合“1..5”中含有5个元素,因此CARD函 数的返回值为5。 proc optmodel; put (card(1..5)); quit; (4)CROSS表达式 在PROC OPTMODEL中,CROSS表达式的作用是返回两个集合的 卡氏集。生成卡氏集的基数为原先两个集合的基数之积。其语法如下: 集合1 CROSS 集合2 以下代码中,集合ORIGIN和DESTINATION分别为初始地和终点。 使用CROSS表达式生成的卡氏集ROUTE包含初始地与终点的所有可能 组合(路线),卡氏集的基数为6。 proc optmodel; set<string> ORIGIN= {'北京', '保定'}; set<string> DESTINATION = {'福州', '泉州', '厦门'}; set<string, string> ROUTE = ORIGIN cross DESTINATION; put'ROUTE is: ' ROUTE ; quit; 运行上述代码,在日志中,输出结果如下: ROUTE is:{<'北京','福州'>},{'北京','泉州'},{'北京','厦门'},{'保定','福州'}, {'保定','泉州'},{'保定','厦门'} (5)DIFF表达式 在PROC OPTMODEL中,DIFF表达式的作用是返回两个集合之 差,即由存在于集合1中但不属于集合2的全体元素所组成的集合。DIFF 表达式的语法如下: 集合1 DIFF 集合2 在以下代码中,集合DESTINATION3包含的是目的地 DESTINATION1以及目的地DESTINATION2之差。运行下面代码: proc optmodel; set<string> DESTINATION1 = {'福州', '泉州', '厦门'}; set<string> DESTINATION2 = {'厦门'}; set<string> DESTINATION3 =DESTINATION1 diff DESTINATION2; put'DESTINATION3 is: ' DESTINATION3; quit; 输出结果为: DESTINATION3 is:{'福州','泉州'} (6)INTER表达式 在PROC OPTMODEL中,INTER表达式的作用是返回两个集合的 交集,即由既属于集合1又属于集合2的全体元素组成的集合。其语法如 下: 集合1 INTER 集合2 例如,以下代码中,集合DESTINATION3包含的是目的地 DESTINATION1以及目的地DESTINATION2的交集。运行下面代码: proc optmodel; set<string> DESTINATION1 = {'福州', '泉州', '厦门'}; set<string> DESTINATION2 = {'厦门'}; set<string> DESTINATION3 =DESTINATION1 inter DESTINATION2; put 'DESTINATION3 is: ' DESTINATION3; quit; 日志中的输出结果为: DESTINATION3 is:{'厦门'} (7)SYMDIFF表达式 在PROC OPTMODEL中,SYMDIFF表达式的作用是返回两个集合 除交集外的部分所组成的集合。其语法如下: 集合1 SYMDIFF 集合2 例如,有以下代码: proc optmodel; set<string> ORIGIN = {'北京', '保定'}; set<string> ORIGIN2 = {'北京', '泉州'}; put (ORIGIN symdiff ORIGIN2); quit; 输出结果为: {'保定','泉州'} (8)IF THEN ELSE表达式 IF THEN ELSE不仅可以用来控制程序的流程,还可以用来处理索 引集,二者的语法相同。具体有,当IF THEN ELSE用来处理索引集 时,语法如下: IF 逻辑表达式 THEN 表达式1[ELSE 表达式2] 如果逻辑表达式为真(非零或者非缺失值),那么执行表达式1; 否则,执行表达式2。ELSE语句与最近的IF-THEN语句匹配。 例如,某一库存优化模型,其模型内的周期为周。库存计算公式如 下: 第i周期末的库存=第i周期初的库存+本周进货量-本周出货量 =第(i-1)周期末的库存+本周进货量-本周出货量 在该优化模型的代码中,参数T为模型考虑的周期个数,变量inv、 order与sell分别表示库存量、订货量与出货量,参数inv0为起始库存 量。约束条件iflow表示上述库存计算公式。由于初始阶段(第1周)的 库存量是inv0,因此,必须对i=1与i>1分开考虑。在这种情况下,可以 使用条件语句区分不同的i取值,具体代码如下: proc optmodel; number T; var inv{1..T}, order{1..T}; number sell{1..T}; number inv0; /* 其他代码*/ con iflow{i in 1..T}: inv[i] = order[i] - sell[i] + if i=1 then inv0 else inv[i-1]; /* 其他代码*/ quit; (9)IN|NOTIN表达式 在PROC OPTMODEL中,IN|NOTIN表达式的作用是判定元素是属 于某个集合,其形式如下: 表达式 IN 集合 表达式 NOTIN 集合 如果表达式在集合中,那么“表达式IN集合”返回值为1;否则,返 回值为0。类似地,如果表达式不在集合中,那么“表达式NOTIN集 合”返回值为1;否则,返回值为0。例如,以下代码中,两个PUT语句 的输出结果均为1。 proc optmodel; set<string> DESTINATION = {'福州', '泉州', '厦门'}; put('福州' IN DESTINATION); put('北京' NOTIN DESTINATION); quit; (10)MAX|MIN集成表达式 在PROC OPTMODEL中,MAX集成表达式与MIN集成表达式是与 集合连用的,作用是返回集合中所有项代入表达式后的最大值与最小 值。其语法如下: MAX{集合}表达式 MIN{集合}表达式 在以下代码中,表达式为虚拟参数的倒数,因此,MAX集成表达 式返回值0.5,MIN集成表达式返回值为0.2。 proc optmodel; put (max{i in 2..5} 1/i); put (min{i in 2..5} 1/i); quit; (11)PROD集成表达式 在PROC OPTMODEL中,PROD集成表达式用于返回所有项对应集 合表达式取值的积。其语法如下: PROD {索引集}集合表达式 如果索引集为空集,那么PROD表达式的返回值为0。在下面的代码 中,PROD表达式计算从1到n的连乘,因此,其输出结果为120。 proc optmodel; number n =5; put(prod{i in 1..5} i); quit; (12)SLICE表达式 在PROC OPTMODEL中,SLICE表达式用于返回一个低维的索引 集,其语法如下: SLICE(<元素1, 元素2, …, 元素n>, 集合) 表达式中前半部分“元素1”、“元素2”、…、“元素n”等的取值必须是 数值表达式、字符表达式或者“*”,且至少包含一个“*”。新生成的索引 集的维数由“*”的个数决定。 在如下代码中,ROUTE表示的是所有路线的集合。表达 式“slice(<'北京',*>,ROUTE)”的作用是,抽取ROUTE所有出发地 为“北京”的路线的终点。第二个SLICE表达式的作用是,抽取在所有 ROUTE中,终点位置为“厦门”的出发地组成的集合。在第三个PUT语句 中,SLICE抽取了所有第二个位置为2的元素的其余位置,组成了新的 集合。 proc optmodel; set<string,string> ROUTE ={<'北京','福州'>,<'北京','泉州'>,<'北京','厦门'>, <'保定','福州'>,<'保定','泉州'>,<'保定','厦门'>}; set<string> DESTINATION1 = slice(<'北京', *>, ROUTE); put DESTINATION1; *first put statement; set<string> DESTINATION2 = slice(<*, '厦门'>, ROUTE); put DESTINATION2; *second put statement; put (slice(<*,2, *>, {<1, 2, 3>, <2, 3, 4>, <5, 2, 6>})); *third put statement; quit; 执行上述代码,第一个PUT语句的输出结果为: {'福州','泉州','厦门'} 第二个PUT语句的输出结果为: {'北京','保定'} 第三个PUT语句的输出结果为: {<1, 3>, <5, 6>} (13)UNION表达式 在PROC OPTMODEL中,UNION表达式的作用是返回两个集合的 并集,即由两个集合中所有不重复的元素组成的集合,其语法如下: 集合1 UNION 集合2 例如,有以下代码: proc optmodel; set<string> ORIGIN1= {'北京', '保定'}; set<string> ORIGIN2 = {'石家庄','北京'}; put(ORIGIN1 UNION ORIGIN2); quit; 其输出的结果为: {'北京','保定','石家庄'} (14)WITHIN表达式 在PROC OPTMODEL中,WITHIN表达式的作用是判断一个集合是 否包含在另外一个集合中,其语法如下: 集合1 WITHIN 集合2 如果集合1包含在集合2中,那么返回值为1;反之,返回值为0。此 外,WITHIN表达式还可以与关键字NOT联用,语法如下: 集合1 NOT WITHIN 集合2 关键字NOT表示对结果取反:原先返回值为1的,使用NOT以后返 回值为0;原先返回值为0的,使用NOT以后返回值为1。例如,以下代 码中,第一个PUT语句的输出结果为1,第二个PUT语句的输出结果为 0,第三个PUT语句的输出结果为1。 proc optmodel; set<string> ORIGIN1= {'北京', '保定'}; set<string> ORIGIN2= {'北京'}; set<string> ORIGIN3= {'北京', '厦门'}; put (ORIGIN2 within ORIGIN1); *first put statement; put (ORIGIN3 within ORIGIN1); *second put statement; put (ORIGIN3 NOT within ORIGIN1); *third put statement; quit; (15)SETOF集成表达式 在PROC OPTMODEL中,SETOF集成表达式必须和集合连用,作 用是根据表达式对索引集中的项一一进行计算或处理,并返回结果。 SETOF集成表达式的语法如下: SETOF{索引集}表达式 例如,假设ARCS是从出发地到目的地的路线。如果想要得到所有 出发地的集合FROM,那么得用SETOF集成表达式,具体代码如下: proc optmodel; set<str, str> ARCS =/ <北京,海南>, <天津, 广州>/; set<str> FROM = setof{<i, j> in ARCS}<i>; put FROM; quit; 在上述代码中,SETOF集成表达式会对ARCS中的每一个项都进行 处理——抽取ARCS的第一个坐标。因此,集合FROM的值为: {'北京', '天津'} 表达式中的计算也可以是多维的。例如,以下代码中,对于每一个 项i,都进行一次多维计算。 proc optmodel; put (setof{i in 1..3}<i, i*i, i**3>); quit; 运行上述代码,输出结果如下: {<1, 1, 1>, <2, 4, 8>, <3, 9, 27>} 后面将会介绍更多有关流程控制以及索引集处理的例子。 22.2 模型的更新 模型的更新主要包含两个方面,一方面是对模型的现有约束条件进 行探索和简化,以达到提高求解效率的目的;另一方面是更新模型中的 参数、决策变量、约束条件或者目标函数,重新进行求解。 22.2.1 使用预求解器 一般来说,优化问题包含的约束条件越多越不容易求解,而且问题 的约束条件之间可能存在着相互包含的关系。在求解过程中,如果能够 去掉“冗余”的约束条件或者简化现有的约束条件,可能会提高求解的效 率。在求解过程中,默认情况下,PROC OPTMODEL会自动调用预求 解器(PRESOLVER)对问题中的线性约束(包括决策变量的边界约 束)进行预处理,以达到对模型更新的目的。预处理包括以下几个方 面: ·将线性约束条件转化成决策变量的边界约束条件。 ·通过检查约束条件之间的关系,删除部分(或全部)“冗余”的约 束条件。 ·将取值固定的决策变量代入约束条件,以简化约束条件。 一般来说,对于小规模问题,使用不使用预求解器对求解时间不会 有太大影响。但是当问题规模较大时,决策变量之间的关系往往会变得 很复杂,这时候使用预求解器可能会提高求解效率。先来考虑以下几个 约束条件: 根据第2个约束条件,可以得到y≤7-x。如果第1个约束条件成立, 那么第3个约束条件y≤4也自然成立。也就是说,第3个约束条件包含在 其余两个约束条件中,因此,理论上,可以去掉第3个约束条件。 实际中,预处理的过程要比上述例子复杂得多。事实上,PROC OPTMODEL的预求解器可能要进行多轮的检查,查看当前约束条件中 是否仍然包含了“冗余”的约束条件或者可简化的约束条件。 预求解器的语法格式比较简单,具体如下: SOLVE WITH LP/PRESOLVER = 关键字 其中,可供选择的关键字有:AUTOMATIC、NONE、BASIC、 MODERATE、AGGRESSIVE。 上述5个关键字也可以分别使用数字-1、0、1、2、3代替。指定不 同的关键字,预处理的程度也不一样,将上述5个关键字按照预处理层 级从低到高排列如下: NONE < BASIC < MODERATE < AUTOMATIC < AGGRESSIVE 应该注意的是: OPTMODEL求解时间=预求解器处理时间+OPTMODEL对预处理后 模型的求解时间 使用预求解器缩短的是上述公式中第二部分的时间,但是同时也会 不可避免地增加第一部分的时间。因此,在某些情形下,虽然预求解 器“简化”了约束条件,但整个OPTMODEL的求解时间反而变得更长。 因此,在实际运用中,应该结合具体问题来选择合适层级的预求解器。 需要指出的是,并不是求解所有的带线性约束的问题都需要进行预 处理。当遇到以下两种情形之一时,不应该考虑使用预处理: ·使用了选项BASIS=WARMSTART时。该选项是单纯形算法的一个 选项。在对初始模型进行更新时,可以考虑使用该选项。该选项的基本 思想是基于原问题的最优解对新的模型进行求解。由于充分利用了原问 题的解的信息,一般来说,求解效率会更高。 ·使用选项IIS=ON对约束条件的不可行性进行判断。这里的IIS集 (Irreducible Infeasible Set)指的是在线性规划中,由若干个约束条件 (包含变量约束)组成的一个不可行集。IIS集的特征是:在移除IIS集 中的任何一个约束条件后,这个集合就不再是一个不可行集。通常来 说,IIS集可能不止一个。选项IIS=ON的作用就是找到这样的一个不可 行集。因此,在使用该选项时必须要禁用预求解器,以防预求解器移除 任何约束条件。该选项在分析模型求解结果为无可行解的情形时很有帮 助。 若遇到以上情形,应将“PRESOLVER=关键字”中的关键字设置为 NONE或者0。 例22.1:假设上述约束条件中的目标函数为f(x,y)=x+2*y。现 在要分别指定不同层级的预求解器来进行求解。 示例代码如下: proc optmodel; var x>=3; var y; con c1: y<=4; con c2: x+y<=7; max f=x+2*y; solve with lp/presolver = NONE; solve with lp/presolver = automatic ; quit; 运行上述代码,输出的日志如图22.3所示。从图中可以看出,相对 于不使用预求解器,指定PRESOLVER=AUTOMATIC(系统默认选 项),预求解器移除了一个约束条件。 图22.3 22.2.2 指定不同层级的预求解器日志对比 决策变量的增加、固定与限制 在建模时,可能会出现一些变化,比如:决策变量的个数增加,某 个或者某几个决策变量的取值需要进行固定,某个或者某几个决策变量 新增加了上限或者下限的约束条件等。这些变化都可能导致原来的模型 不再适用,需要对模型进行更新。来看一个模型更新的例子。 假设X公司生产4种产品,分别记为A、B、C和D。生成上述4种产 品所需要的原材料为R1和R2。在上述4种产品中,生产每单位产品所需 要的原材料、人力以及每单位产品的售价如表22.1所示。 表22.1 生产单位产品所需要的原材料、人力以及单位产品售价 单位原材料、单位人力的费用以及每种资源可供使用的总额(包括 人力)如表22.2所示。 表22.2 每种资源的单位费用以及可用资源总额 以下代码根据表22.1以及表22.2的信息分别生成了两个数据集 product_data和resource_data。 data product_data; input Item $ Selling_Price R1 R2 Labor; datalines; A 1100 7 3 1 B 1000 10 5 1 C 950 5 4 1 D 1000 1 3 6 run; data resource_data; input Resource $ Cost Amount_Available; datalines; R1 10 1000 R2 6 850 Labor 8 1000 run; 情形一:X公司设计出一种新的产品E,生产该产品所需要资源与 目前其他产品的生产所需要的原料一致。生产单位产品E所需的资源以 及售价如表22.3所示。 表22.3 新增产品E的信息 此时,应该如何制定生产计划使得经济效益最大化? 原先产品的种类只有4种,因此,模型中的对应决策变量个数为4 个。现产品种类增加到5种,模型中的变量个数也由原来的4个增加到了 5个。因此,需要对模型进行相应的调整。 例22.2:模型更新(一)——决策变量的增加。以下代码在读入原 始数据集后,使用集合运算符UNION对索引集PRODUCT进行了更新, 并更新了数组Required。 proc optmodel; /* declare sets and parameters */ set<str> PRODUCTS, RESOURCE; num Cost{RESOURCE}, Availability{RESOURCE}; num Selling_Price{PRODUCTS}; num Required{PRODUCTS, RESOURCE}; /* read data from SAS data sets */ read data resource_data into Resource=[Resource] Cost Availability=Amount_Available; read data product_data into PRODUCTS=[Item] {r in RESOURCE} <Required[item, r]=col(r)> Selling_Price; var x{PRODUCTS} >= 0; /* for {i in ITEMS: i = 'A'} fix x[i] = 40;*/ impvar Revenue = sum{p in PRODUCTS}Selling_Price[p]*x[p]; impvar Amount_Used{r in RESOURCE} = sum{p in PRODUCTS} Required[p, r]*x[p]; impvar Total_Cost = sum{r in RESOURCE} Cost[r]*Amount_Used[r]; impvar Profit{p in PRODUCTS} = ( Selling_Price[p] Cost[r]*Required[p, r] ) * x[p]; /* declare constraints */ con Usage{r in RESOURCE}: Amount_Used[r] <= Availability[r]; PRODUCTS = PRODUCTS union /E/; Selling_Price['E'] = 1000; Required['E', 'R1'] = 2; Required['E', 'R2'] = 4; Required['E', 'labor'] = 3; /* declare objective */ max Net_Profit = Revenue - Total_Cost; solve ; print x; quit; 代码的输出结果如图22.4所示。 sum{r in RESOURCE} 图22.4 新增变量后模型的求解结果 需要注意的是,对索引集添加新值,必须在READ语句之后,否则 程序将出错。例如,在上述代码中,假定将语 句“PRODUCTS=PRODUCTS union/E/;”移到READ语句前,如下: proc optmodel; /* declare sets and parameters */ set<str> PRODUCTS, RESOURCE; num Cost{RESOURCE}, Availability{RESOURCE}; num Selling_Price{PRODUCTS}; num Required{PRODUCTS, RESOURCE}; PRODUCTS = PRODUCTS union /E/; read data resource_data into Resource=[Resource] Cost Availability=Amount_Available; /***************************** 其他代码**********************************/ 运行上述代码,PROC OPTMODEL在日志中将输出如下错误信 息: Error:The Location‘PRODUCTS’has no value at行31列9。 对于上述决策变量的增加,我们进行了模型的更新,这种处理方法 并未涉及对原始数据集的修改。另一种处理方法是:不对模型进行修 改,而是对原始数据集进行一些操作(如新增观测)。具体得取决于实 际情况,读者可以选择其中的一种方法进行处理。 1.FIX语句与UNFIX语句 情形二:在情形一的计算结果中,B和C的计划生产数量为0。公司 管理人员认为这不太符合市场需求,会引起客户的流失。为此,决定将 B和C的生产数量定为30和40。在这种情况下,应该如何制定生产计划 使得利润最大化? 与原来问题相比,产品B和产品C的生产数量固定了,该模型可行 解的形式如下: X=(XA,XB,XC,XD,XE)=(XA,30,40,XD,XE), 其中,XA,XB,XC,XD与XE分别代表产品A、B、C、D以及E的 生产数量。 当我们需要固定某个或者某几个决策变量取值时,可以考虑使用 FIX语句。FIX语句的一般形式如下: FIX 变量[索引集的项][=表达式] 其中: ·变量的个数可以是多个,多个变量之间用空格隔开。值得注意的 是,在这种情况下,这些变量的赋值是一样的(均等于表达式的计算结 果)。如果多个变量要分别赋给不同的值,可以使用多个FIX语句。 ·如果省略索引集的项,FIX语句固定的是整个数组变量。以上面模 型的解X为例,语句FIX X['B']=30仅将数组变量X中X[B]的值固定为 30,而语句FIX X=30则将数组变量中所有的变量都固定为30了。 ·若表达式缺失,那么变量的值将被固定为该变量的当前值。例 如,若紧接着SOLVE语句之后提交FIX语句,那么变量值将会被固定为 当前最优解。 UNFIX语句执行的是与FIX语句相反的操作——取消对决策变量的 固定。其语法格式、使用方法与FIX语句一致,这里就不重复介绍了。 例22.3:模型更新(二)——使用FIX语句对决策变量的取值进行 固定。下面的代码在考虑将产品B与产品C的生产数量分别固定为30和 40的情况下,对模型进行更新、求解。 proc optmodel; /* declare sets and parameters */ set<str> PRODUCTS, RESOURCE; num Cost{RESOURCE}, Availability{RESOURCE}; num Selling_Price{PRODUCTS}; num Required{PRODUCTS, RESOURCE}; /* read data from SAS data sets */ read data resource_data into Resource=[Resource] Cost Availability=Amount_Available; read data product_data into PRODUCTS=[Item] {r in RESOURCE} <Required[item, r]=col(r)> Selling_Price; var x{PRODUCTS} >= 0; /* for {i in ITEMS: i = 'A'} fix x[i] = 40;*/ impvar Revenue = sum{p in PRODUCTS}Selling_Price[p]*x[p]; impvar Amount_Used{r in RESOURCE} = sum{p in PRODUCTS} Required[p, r]*x[p]; impvar Total_Cost = sum{r in RESOURCE} Cost[r]*Amount_Used[r]; impvar Profit{p in PRODUCTS} = ( Selling_Price[p] - sum{r in RESOURCE} Cost[r]*Required[p, r] ) * x[p]; /* declare constraints */ con Usage{r in RESOURCE}: Amount_Used[r] <= Availability[r]; PRODUCTS = PRODUCTS union /E/; Selling_Price['E'] = 1000; Required['E', 'R1'] = 2; Required['E', 'R2'] = 4; Required['E', 'Labor'] = 3; /* declare objective */ max Net_Profit = Revenue - Total_Cost; fix x['B'] = 30; fix x['C'] = 40; solve ; print x; quit; 运行上述代码,输出结果如图22.5所示。 图22.5 使用FIX语句后模型的输出结果 2..UB下缀与.LB下缀 情形三:在情形二中的最优解中,D的生产数量为126.667。经过研 究,公司认为生产计划中,产品D的数量不应该超过100个,产品A的数 量和产品E的数量不得少于20个。在这种情形下,应该如何制定生产计 划使得利润最大化? 例22.4:模型更新(三)——使用.UB以及.LB对变量的上限以及下 限进行设置。以下代码在FIX语句之后,使用.UB下缀以及.LB下缀分别 设置了产品D的产量上限以及产品A、E的产量下限,从而对情形三进行 建模求解。 proc optmodel; /* declare sets and parameters */ set<str> PRODUCTS, RESOURCE; num Cost{RESOURCE}, Availability{RESOURCE}; num Selling_Price{PRODUCTS}; num Required{PRODUCTS, RESOURCE}; /* read data from SAS data sets */ read data resource_data into Resource=[Resource] Cost Availability=Amount_Available; read data product_data into PRODUCTS=[Item] {r in RESOURCE} <Required[item, r]=col(r)> Selling_Price; var x{PRODUCTS} >= 0; impvar Revenue = sum{p in PRODUCTS}Selling_Price[p]*x[p]; impvar Amount_Used{r in RESOURCE} = sum{p in PRODUCTS} Required[p, r]*x[p]; impvar Total_Cost = sum{r in RESOURCE} Cost[r]*Amount_Used[r]; impvar Profit{p in PRODUCTS} = ( Selling_Price[p] - sum{r in RESOURCE} Cost[r]*Required[p, r] ) * x[p]; /* declare constraints */ con Usage{r in RESOURCE}: Amount_Used[r] <= Availability[r]; PRODUCTS = PRODUCTS union /E/; Selling_Price['E'] = 1000; Required['E', 'R1'] = 2; Required['E', 'R2'] = 4; Required['E', 'Labor'] = 3; /* declare objective */ max Net_Profit = Revenue - Total_Cost; fix x['B'] = 30; fix x['C'] = 40; x['D'].ub = 100; x['A'].lb = 20; x['E'].lb = 20; solve ; print x; print amount_used; quit; 运行上述代码,输出结果中的最优值以及最优解如图22.6所示。 图22.6 使用.LB以及.UB下缀后模型的输出结果 从图22.6可以看出,在最优解中,产品A和E的数量的确超过了20 个、产品D的数量下降到了100个(达到上限)。 22.2.3 约束的改变与放松 情形四:在情景三的基础上,进一步假设有一批额外的原材料R1可 以利用,总量不超过200个单位,这批额外的原材料每单位成本较存库 里的原材料成本上升0.5元。在这种情况下,应该如何制定生产计划使 得利润最大化? 例22.5:模型更新(四)——修改约束条件。这里对模型进行如下 更新:定义新的变量extra_r1,更改模型中资源的约束条件Usage以及总 成本Total_Cost,并输出资源的使用量amount_used。具体代码如下所 示: proc optmodel; /* declare sets and parameters */ set<str> PRODUCTS, RESOURCE; num Cost{RESOURCE}, Availability{RESOURCE}; num Selling_Price{PRODUCTS}; num Required{PRfiODUCTS, RESOURCE}; /* read data from SAS data sets */ read data resource_data into Resource=[Resource] Cost Availability=Amount_Available; read data product_data into PRODUCTS=[Item] {r in RESOURCE} <Required[item, r]=col(r)> Selling_Price; var x{PRODUCTS} >= 0; var extra_r1 >=0<=200; impvar Revenue = sum{p in PRODUCTS}Selling_Price[p]*x[p]; impvar Amount_Used{r in RESOURCE} = sum{p in PRODUCTS} Required[p, r]*x[p]; impvar Total_Cost = sum{r in RESOURCE} Cost[r]*Amount_Used[r]+0.5*extra_r1 ; impvar Profit{p in PRODUCTS} = ( Selling_Price[p] - sum{r in RESOURCE} Cost[r]*Required[p, r] ) * x[p]; /* declare constraints */ con Usage{r in RESOURCE}: Amount_Used[r] <= Availability[r] + if (r='R1') then extra_r1; PRODUCTS = PRODUCTS union /E/; Selling_Price['E'] = 1000; Required['E', 'R1'] = 2; Required['E', 'R2'] = 4; Required['E', 'Labor'] = 3; /* declare objective */ max Net_Profit = Revenue - Total_Cost; fix x['B'] = 30; fix x['C'] = 40; x['D'].ub = 100; x['A'].lb = 20; x['E'].lb = 20; solve ; print x; print amount_used; print extra_r1; print Availability ; quit; 运行上述代码,输出结果中的最优解、目标函数最优值以及资源使 用情况Amount_Used如图22.7所示。 图22.7 修改约束条件Usage后模型的最优解与最优值 从Amount_Used的使用情况可以看出,人力资源并没有得到充分利 用(可供支配的Labor共有1000小时)。此外,R1与R2均已达到上限。 为了使得人力资源得到进一步的利用,现假设R2的资源没有上限。在这 种情况下,应该如何制定生产计划使得利润最大化? 情景五:假设R1的约束条件与上例相同。若暂不考虑原材料R2的 约束,超过库存部分的原材料R2,每单位成本不变。为了使得人类资源 得到进一步发挥,该如何制定生产计划? 情景五实际上放松了与R2相关的约束条件。在PROC OPTMODEL 中,放松约束条件可以通过DROP语句实现。使用该语句可放松指定的 一个或者一组约束,其使用语法如下: DROP 约束[索引集的项]; 其中: ·如果语句省略索引集的项,那么DROP语句放松整个约束组。 ·如果有多个约束组,那么约束组用空格隔开。 例22.6:模型更新(五)——放松约束条件。考虑情形五,对上例 中的模型进行如下更新,修改变量Total_Cost,放松约束条件Usage中与 R2相关的部分。具体代码如下: proc optmodel; /* declare sets and parameters */ set<str> PRODUCTS, RESOURCE; num Cost{RESOURCE}, Availability{RESOURCE}; num Selling_Price{PRODUCTS}; num Required{PRODUCTS, RESOURCE}; /* read data from SAS data sets */ read data resource_data into Resource=[Resource] Cost Availability=Amount_Available; read data product_data into PRODUCTS=[Item] {r in RESOURCE} <Required[item, r]=col(r)> Selling_Price; var x{PRODUCTS} >= 0; var extra_r1 >=0<=200; impvar Revenue = sum{p in PRODUCTS}Selling_Price[p]*x[p]; impvar Amount_Used{r in RESOURCE} = sum{p in PRODUCTS} Required[p, r]*x[p]; impvar Total_Cost = sum{r in RESOURCE}Cost[r]*Amount_Used[r]+0.5*extra_r1; impvar Profit{p in PRODUCTS} = ( Selling_Price[p] - sum{r in RESOURCE} Cost[r]*Required[p, r] ) * x[p]; /* declare constraints */ con Usage{r in RESOURCE}: Amount_Used[r] <= Availability[r] + if (r='R1') then extra_r1 ; PRODUCTS = PRODUCTS union /E/; Selling_Price['E'] = 1000; Required['E', 'R1'] = 2; Required['E', 'R2'] = 4; Required['E', 'Labor'] = 3; /* declare objective */ max Net_Profit = Revenue - Total_Cost; fix x['B'] = 30; fix x['C'] = 40; x['D'].ub = 100; x['A'].lb = 20; x['E'].lb = 20; drop Usage['R2']; solve ; print x; print amount_used; quit; 运行上述代码,输出结果如图22.8所示。 图22.8 放松约束条件后模型的输出结果 从上述输出结果可以看出,由于放松R2的限制,使Labor值达到了 上限,即人力资源得到了充分利用。 22.3 网络流模型 在PROC OPTMODEL中,常见的用于求解线性规划的算法有原始 单纯形法、对偶单纯形法和内点法等(上述3种方法详见第20章)。本 节要介绍的是PROC OPTMODEL中的另外一种算法——网络单纯形算 法(Network Simplex Algorithm)。 22.3.1 网络流模型概述 在优化中,一个网络是由若干个节点及一些连接两节点间的弧组成 的。在实际应用中,有些待研究的问题可以用网络来描述:节点代表问 题中的研究对象,弧描述的是对象之间的关系。例如,从A地到B地的 所有可能航空路线构成了一个网络:节点是从A地到B地的所有可能线 路上的城市,当两个节点之间存在一个直达的航线时,我们称这两个节 点之间存在一条弧。网络不仅可以用来描述实体的网络结构,还可以用 来描述关系型网络,甚至可以描述某个对象的历史。例如,某大学的教 学秘书在学期初需要根据本学期的教学任务,安排教师的教课计划。在 这种情形下,教师以及课程构成了一个关系型网络图,网络中的节点代 表了教师以及课程,弧描述的是两个节点之间是否存在教学关系(教师 教某课程)。又如,某汽车品牌的4S店的历史月末库存水平也可以用网 络来描述:节点为历史上每月的库存水平、上游供应商以及消费者(看 成一个节点),弧可用来描述节点间是否存在供或求关系。 弧不仅可以用来描述“存在与不存在”的关系,还可以给弧赋予方向 和权重。比如,考虑上述从A地到B地的路线图,弧的方向即为飞机在 两个节点(城市)之间的航行方向,权重则可以设置为两个节点之间的 机票价格。 考虑一类具有特殊结构的网络,该网络具有如下特征: ·网络中的弧带有方向性。 ·网络节点中,存在两个特殊的节点。 ·网络中每一条弧都对应着一个非负值。 上述网络中,两个特殊的节点分别称为源(起始点)与汇(终 点),每条有向弧对应的非负值称为该弧的容量。进一步,考虑定义在 有向弧上的一个非负函数,对于每一个特定的有向弧,该非负函数的取 值不超过该弧的容量。我们称该非负函数为定义在弧上的可行流。这种 带“流”的网络也称网络流。例如,有一批货物要从A地发送到B地,那 么从A地到B地的所有可能路线就组成一个网络。节点A为源,节点B为 汇,每条弧都是有向弧,每一条路线都有一定的容量,可行流可以定义 为经由某条具体航线(弧)运送的货物数量。 在考虑网络流的模型中,要注意隐含的约束条件。除了每个节点上 的流可能有上限的约束外,还要考虑流的平衡问题,即节点上流入量与 流出量是平衡的。值得注意的是,平衡既可以指流出量不超过流入量, 也可以指流出量等于流入量,平衡的具体定义主要取决于实际问题。 假设有任一网络流,如图22.9所示。图中显示的是某节点k上的 流,变量名称以及意义分别如图中所示。 图22.9 节点上的流的约束 若节点上的流是平衡的,现将该平衡关系以PROC OPTMODEL的 语法形式表示,如下(NODES是节点的索引集): Con Flow_Balance{k in NODES } : Flow_In[k]+supply[k] = Flow_Out[k] + Demand[k]; 除了节点的流有约束以外,节点本身的容量可能也是有限制的。假 设节点k容量的上限为Capacity[k]。那么,该约束可以写成: Con 22.3.2 Capacity{k in NODES}: Flow_In[k]+supply[k] <= Capacity[k]; 使用OPTMODEL求解网络流模型 在PROC OPTMODEL中,网络流模型的求解可以通过调用的网络 单纯形求解器(Network simplex solver)来实现。由于考虑了网络的特 殊结构,使用网络单纯形求解器对网络流模型进行求解往往较其他求解 器的效率要高。调用网络单纯形求解器的命令如下: solve with lp / solver=ns; 下面以一个例子来说明如何建立网络模型,并调用网络单纯形求解 器对模型求解。 例22.7:Y公司是一家外贸公司,公司在美国境内设有3个中转站, 在中国境内也有若干中转站。现有一批货物需要从多伦多转运到北京, 路线图信息如图22.10所示。 图22.10 转运路线图 转运图中的各路线的费用如下所示: data route; input Origin $13. Destination $14.cost; datalines; Toronto Chicago 105 Toronto Los Angel 120 Toronto San Francisco 135 Chicago ShangHai 600 Chicago TianJin 621 Los Angel ShangHai 680 Los Angel TianJin 650 Los Angel HongKong 610 San Francisco TianJin 710 San Francisco HongKong 650 ShangHai BeiJing 55 TianJin BeiJing 45 HongKong BeiJing 100 ; run; 转运图中各个节点现有的剩余库容如下: data capacity; input Warehouse $13. Capacity; datalines; Toronto 1500 Chicago 300 Los Angel 350 San Francisco 200 HongKong 400 ShangHai 300 TianJin 300 BeiJing 2000 ; run; 假设有800吨的货物要从多伦多发往北京,基于上述的物流路线 图、费用以及库容,应该如何设计路线,才能使得总费用最小?最小费 用是多少? 对于上述问题,对于网络结构中的每一个节点k,要考虑如下两个 约束: ·流进某个节点的流的上限是剩余库容。 ·节点上流的平衡。 在考虑上述约束的前提下,模型的目标是要使得费用最小。 定义如下参数以及变量。 ·参数Cost[i,j]:从i到j的费用。 ·参数Demand[k]:第k个节点的需求。当节点为北京时,取值为 800,否则为0。 ·参数Supply[k]:第k个节点的供应。当节点为多伦多时,取值为 800,否则为0。 ·参数Capacity[k]:第k个节点的库容。 ·变量x[i,j]:经由节点i发往节点j的货物数量。 将上述目标以及约束以数学公式描述为如下形式: 使得 Flow_In[k]+Supply[k]=Flow_Out[k]+Demand[k],k∈Nodes Flow_In[k]+Supply[k]≤Capacity[k],k∈Nodes 其中,索引集Nodes与ARCS分别对应网络图中的节点(各中转站、 多伦多与北京)与弧(路线)。 根据以上模型,可以使用PROC OPTMODEL对其进行求解。具体 代码如下: proc optmodel; /* declare sets and parameters */ set<str,str> Arcs; set<str> Nodes ; num Cost{Arcs}; num Capacity{Nodes}; num Supply{Nodes} init0; num Demand{Nodes} init0; /* read data from SAS data sets */ read data route into Arcs=[Origin Destination] Cost; read data capacity into Nodes = [Warehouse] Capacity; /* assign supply and demand values */ Supply['Toronto'] = 800; Demand['BeiJing'] = 800; /* declare variables */ var x{Arcs} >= 0<= 350; impvar Flow_In{k in Nodes} = sum{<i,(k)> in Arcs} x[i,k]; impvar Flow_Out{k in Nodes} = sum{<(k),j> in Arcs} x[k,j]; /* declare constraints */ con Flow_Balance{k in Nodes}: Flow_In[k] + Supply[k] = Flow_Out[k] + Demand[k]; con Flow_Node{k in Nodes}: Flow_In[k] + Supply[k]<=Capacity[k]; /* declare objective */ min Total_Cost = sum{<i,j> in Arcs} Cost[i,j] * x[i,j]; solve with lp / solver=ns; create data network_flows from [Origin Destination]= {<i,j> in Arcs: x[i,j]>0} Amount=x; quit; 运行上述代码,解的汇总信息以及最优解数据集Network_flow如图 22.11所示。 根据上面的输出,最佳路线如图22.12所示,整个路线的费用为 646750。 图22.11 使用网络流模型设计最佳线路 图22.12 最佳路线图 22.4 本章小结 本章讲解了PROC OPTMODEL程序设计方面的知识,涉及流程的 控制、索引集的处理以及模型的更新。对于线性规划中的一类特殊问题 ——网络流问题,可以调用PROC OPTMODEL中的网络单纯形求解 器,一般来说,该求解器比其他的求解器效率要高。 第23章 整数线性规划和混合整数线性规划 回顾前面介绍的线性规划问题以及运用PROC OPTMODEL建立线 性规划模型并求解,可以发现,在这些线性规划问题中,决策变量的取 值都是连续型的。然而,对于很多实际问题,其实只有当问题的最优解 为整数时才有意义。对于这类问题,不能简单地认为,决策变量为整数 型问题的解就是对决策变量为连续型问题的解取整。研究如何求得整数 最优解的数学规划问题属于运筹学的另一个分支——整数规划。本章将 介绍如何求解整数线性规划问题和混合整数线性规划问题。 23.1 整数线性规划和混合整数线性规划概述 在线性规划中,如果所有决策变量的取值都要求是非负整数,则此 类问题称为纯整数线性规划,简称整数线性规划(Integer Linear Programming,ILP);如果有的决策变量可取非负实数,有的限取整 数,则此类问题被称为混合整数线性规划问题(Mixed Integer Linear Programming,MILP)。在本章中,除非特别说明,线性规划问题指的 都是决策变量为连续型的线性规划问题。线性规划、整数线性规划以及 混合整数线性规划的示例如图23.1所示。 图23.1 3种规划的示例 整数线性规划问题可行解的个数虽然是有限的,但是试图用枚举法 列出该问题的所有整数解,再设法从中找出最优解的想法显然是不现实 的。对于整数线性规划问题,若将其整数限制放开,便得到原整数规划 问题的一个伴随问题。该伴随问题是一个线性规划问题,可以方便地用 单纯形法求解。然而,在求得伴随问题的最优解之后,若试图通过对该 最优解“取整”的途径来得到原整数线性规划问题的最优解,也是行不通 的。 那么,能否在合理剖分可行域的同时,只检验部分整数解,即求得 整数线性规划问题的最优解呢?答案是肯定的,这就是分支定界法 (Branch-and-Bound Method)的基本思想。 23.1.1 分支定界法 分支定界法的基本思想是将原问题分解为若干个分支,通过对各个 分支的求解达到求解原问题的目的。不失一般性,以最大化目标函数的 整数线性规划问题为例,分支定界法的主要步骤为(混合整数线性规划 的求解步骤与整数线性规划的步骤一致): 1)求解原整数线性规划问题的伴随问题。将决策变量的整数约束 放开,这样,整数线性规划问题就转化成为一个线性规划问题。如果伴 随问题的最优解恰好是整数解,那么该最优解即为原整数线性规划的最 优解,求解结束;如果伴随问题无可行解,那么原整数线性规划也无可 行解,求解结束;如果伴随问题有最优解,但是最优解不满足整数的约 束条件,那么进入下一步。 2)分支。对伴随问题最优解中不满足整数约束条件的某一个决策 变量xi(假设xi取值为x*)进行“分支”。记[x*]为小于等于x*的最大整 数。分支的具体做法为:分别将约束条件xi≥[x*]+1和xi≤[x*]加入原整数 线性规划问题中。这时原问题的可行域就被剖分成了两个子可行域,从 而也就分别得到了两个子可行域上的整数线性规划问题,这两个整数线 性规划问题就称为分支。注意,在分支的过程中,去除的区域为[x*] <x<[x*]+1,该区域内并不包含任何整数。 3)定界。分别对每个分支进行求解。求解每个分支的伴随问题, 若该分支的伴随问题没有可行解,对该分支进行剪支(即不再考虑该分 支);若该分支伴随问题的最优解是整数解,那么该分支的最优解是整 数线性规划最优解的一个备选,此时的最优值是原问题目标值的一个下 界。若该分支伴随问题的最优解仍然不满足决策变量的整数约束条件, 并且此时的最优值比已经发现的原问题的目标值下界小,就对该分支进 行剪支;如果此时的最优值比已经发现的目标值下界大,那么它提供了 原问题最优目标值的一个新上界,并转入第2步中,对于该伴随问题最 优解中的某一个非整数决策变量xj(j可以不等于i)进行分支,然后求 解。 4)直到所有的分支都无进一步分支的价值,则比较所有备选分支 的最优解,目标值最大的分支最优解即为原整数线性规划的最优解。 注意 在分支步骤中,不满足整数约束条件的决策变量可能有多 个。在这种情况下,选择不同的决策变量进行分支,求解过程可能不一 样,但是最终求得的最优解肯定是一样的。 例23.1:分支定界法示例。利用分支定界法对图23.1中的整数线性 规划问题进行求解。为了下文叙述的方便,记该整数线性规划问题为 ILP0,ILP0的伴随问题记为LP0,各分支分别记为ILP1、ILP2等,各分 支对应的伴随问题分别记为LP1、LP2等。 首先,对ILP0进行放松。经过放松后的伴随问题(即LP0)为图 23.1中的问题(1)。现在对该线性规划问题进行求解,示例代码如 下: /*lp0*/ proc optmodel; var x>=0 ; var y>=0 ; con 2*x-y<=3; con 3*x+4*y<=9; max f=5*x+4*y; solve; print x y; quit; 运行上述代码,输出结果如图23.2所示。 图23.2 线性规划LP0的最优解及最优值 从图中可以看出,该最优解(x*=1.9091,y*=0.8181)并不满足决 策变量为整数的约束条件。因此,[x*]=1,对线性规划LP0进行分支定 界,如下所示。可以发现,分支ILP2中的约束条件x≥0已经被包含在约 束条件x≥2中了,但为了更好地理解分支定界的过程,这里同时保留这 两个约束条件。 max f=5*x+4*y max f=5*x+4*y s.t.2*x-y≤3 s.t.2*x-y≤3 3*x+4*y≤9 3*x+4*y≤9 x,y≥0 x≤1 x,y≥0 x≥2 分支ILP1 分支ILP2 对分支ILP1与ILP2的伴随问题LP1和LP2分别使用PROC OPTMODEL进行求解,该代码与上面求解LP0的示例代码类似(只需在 声明决策变量时对决策变量的界进行修改),限于篇幅,这里省略代 码。习惯上,可以将分支以树形的方式展示,该树也称分支定界树。图 23.3以分支定界树的形式展示了LP0、LP1以及LP2。 在图23.3中,LP2的可行域为空集,可以对该分支进行剪支。对于 LP1,最优解y*的值不完全满足整数约束条件。因此,根据小于等于y* 的最大整数([y*]=1),对ILP1继续进行分支定界。具体如下所示。 max f=5*x+4*y max f=5*x+4*y s.t.2*x-y≤3 s.t.2*x-y≤3 3*x+4*y≤9 3*x+4*y≤9 x,y≥0 x,y≥0 x≤1 x≤1 y≤1 y≥2 分支ILP3 分支ILP4 下面分别对分支ILP3和ILP4的伴随问题LP3和LP4进行求解,得到 的最优解以及最优值如图23.4所示。 图23.3 LP1和LP2的求解结果 图23.4 分支ILP1~ILP4的求解结果 从图23.4可以看出,分支ILP3的最优解是整数解,因此,该整数解 是原整数规划问题最优解的一个备选,最优值9为原问题目标值的一个 下界。但LP4的最优解仍然不满足决策变量为整数的约束条件,不过, 最优值9.6667为原问题的目标值提供了一个上界,现在要继续对LP4进 行分支定界。完整的分支定界树如图23.5所示。 从图23.5可以看出,LP7的最优解也满足决策变量为整数的约束条 件。因此,分支LP7的最优解也是原整数规划问题最优解的一个备选。 在图23.5中,所有备选解所在的分支均以阴影形式标记了。比较所有备 选解的目标值,可以得出原整数规划的最优解为x*=1,y*=1。 对分支定界法而言,其实施要点可以归纳如下: 1)定界原则如下。 ·对于求最大化问题,子可行域上伴随问题的整数解提供了原问题 最优目标值的下界,伴随问题的非整数解则提供了原问题最优目标值的 上界。 ·对于求最小化问题,子可行域上伴随问题的整数解提供了原问题 最优目标值的上界,伴随问题的非整数解则提供了原问题最优目标值的 下界。 2)分支原则如下。 ·可以选择伴随问题的最优解中不满足整数约束条件的任选一个决 策变量进行分支。 ·若子可行域上的伴随问题不可行,或者子可行域上伴随问题的最 优解是整数,则在该子可行域上不再分支。对于求最大化问题,若伴随 问题最优解所对应的目标值小于已求出的下界,则在该子可行域上不再 分支;对于求最小化问题,若伴随问题最优解所对应的目标值大于已定 出的上界,则在该子可行域上不再分支。 图23.5 分支定界树 23.1.2 割平面法 若原整数线性规划问题的伴随问题最优解为非整数,则设法在其伴 随问题中增加一条约束,并将此非整最优解“割”去;同时要保证原问题 的任一整数可行解不被“割”去。这个新增加的约束条件通常称为割平面 (Cutting Plane)。然后,对增添了割平面的整数线性规划的伴随问题 运用单纯形法求解,直至求得原问题的整数最优解为止。这就是割平面 法的算法思想。 基于上面的算法思想,割平面必须具有以下两个性质: ·割去了相应伴随问题的非整数最优解。 ·不会割去原问题的任一整数可行解。 基于这两个性质,可以建立如下算法步骤: 1)求解原问题的伴随问题,得到最优解。 2)若最优解是整数解,则求解结束;否则,进入第3步。 3)选择某一不满足整数约束条件的决策变量构造割平面,对新增 添了割平面的整数线性规划的伴随问题进行求解,直到求得整数最优 解。 考虑例23.1中的问题。观察图23.6,考虑到决策变量必须为整数的 约束条件,该整数线性规划问题的可行解为(0,0)、(1,0)、 (0,1)、(1,1)和(0,2)。上述可行解都满足不等式x≤1,但是 最优解(1.9091,0.8181)却不满足该不等式。不等式x≤1满足割平面的 两个性质,可以作为是该整数线性规划问题的一个割平面。割去的部分 可行域以及剩余的可行域如图23.6所示。 图23.6 割平面x≤1 构造割平面的方法很多。常见的方法如下。 (1)利用伴随问题的最优解构造 如果伴随问题存在最优解,那么根据前面的介绍可知,该伴随问题 的可行域是凸集,并且其最优解必然可以在可行域的顶点处达到。可 见,由单纯形法所求得的最优解,一定是可行域的顶点。所以,在上述 线性规划问题LP0中,最优解x*=1.9091、y*=0.8181一定可以使得某几 个约束条件成为紧约束条件。稍加考察可知,该最优解同时使得约束条 件3*x+4*y≤9以及2*x-y≤3成为了紧约束条件,即不但满足上述两个约束 条件,而且使得两个约束条件中的等号成立。现在就可以根据这两个约 束条件,构造割平面x≤1,具体步骤如下: 1)不等式2*x-y≤3的两边同时乘以4,得到8*x-4*y≤12。 2)将上一步中的不等式8*x-4*y≤12以及原线性规划中的不等式 3*x+4*y≤9相加,得到新的不等式:11*x≤21,即x≤21/11。 3)由于x取值只能为整数,据此得到x≤1。 (2)根据单个约束条件以及决策变量的上、下界进行构造 例如,利用不等式x≤1,以及3*x+4*y≤9可以构造割平面x+y≤2。具 体做法如下: 4*x+4*y=x+3*x+4*y≤1+3*x+4*y≤10 两边同时除以4,得到: 由于决策变量x与y取值均为整数,因此,x+y的取值也只能为整 数。据此,可以推断: x+y≤2 如果在图23.6中添加一个新的割平面x+y≤2,得到的可行域恰好有 一个顶点满足整数线性规划的约束条件,且该顶点为添加割平面后伴随 问题的最优解,那么可以判定该最优解(1,1)即为整数线性规划问题 的最优解,如图23.7所示。 添加割平面x+y≤2后,求解线性规划问题的代码如下: 运行上述代码后,输出结果如图23.8所示。 图23.7 添加新的割平面后得到最优解 图23.8 添加割平面后伴随问题的最优解 在实际使用中,割平面法一般与分支定界法结合使用,称为分支割 平面算法(Branch-and-Cut Algorithm)。 23.2 使用PROC OPTMODEL求解混合整数线性规划 前面介绍了整数规划以及混合整数规划的求解方法和步骤,本节介 绍如何使用SAS/OR的PROC OPTMODEL对整数线性规划以及混合整数 规划线性进行求解。 在PROC OPTMODEL中,整数变量的定义是通过关键字INTEGER 来实现的。例如,以下代码分别定义了决策变量X,以及以集合ARCS 为索引集的决策变量Cost,两个决策变量规定了只能取整数值。 var x>=0<=2 integer; var Cost{ARCS}>=0 integer; 有的时候,直接调用MILP求解器求解整数线性规划或者混合整数 线性规划,往往需要花费大量的计算资源,这种情况下可以先通过求解 其伴随问题来获得对最优解的一个初步认知。在PROC OPTMODEL 中,使用.RELAX下缀可以放开对决策变量的整数约束。其语法如下: 变量.relax = 某一非零、非缺失值 其中,等号右边的值并无实际意义,只要该值非零或者是非缺失 值,就表示放开对变量的整数约束。如果在优化问题中,决策变量本身 不要求是整数,那么PROC OPTMODEL会忽略该语句。 下面的代码放开了3个变量的整数约束,其中第3个语句放开了整个 变量组的整数约束。 X.relax =1; Cost['New York', 'BeiJing'].relax =1 ; for {k in NODES} Z[k].relax =1; 如果要一次性放开所有决策变量的整数约束,可以在调用求解器时 使用以下语法: Solve relaxint 例23.2:回顾例23.1整数线性规划问题,使用PROC OPTMODEL求 解该问题。 示例代码如下: proc optmodel; var x>=0 integer; var y>=0 integer; con 2*x-y<=3; con 3*x+4*y<=9; max f=5*x+4*y; solve; print x y; quit; 运行上述代码,输出结果如图23.9所示。从图23.9中可以看出, PROC OPTMODEL使用的是MILP求解器,调用的算法是分支割平面法 (Branch and Cut),最优解为x*=1,y*=2。 图23.9 整数线性规划问题的输出结果 在分支割平面法中,与前面介绍的分支定界树相对应的是分支割平 面树,但是这种方法在分支的过程中同时也添加了割平面约束。在使用 MILP求解器对整数线性规划问题进行求解时,日志中输出了分支割平 面法在求解过程中的BestInteger、BestBound、Gap、运行时间等信息, 如图23.10所示。 图23.10 使用分支割平面法的日志信息 其中: ·Sols为当前已经找到的可行解个数。 ·对于最大化问题,BestInteger为当前已经求解到的最优整数解的目 标值中最大的一个,这个目标值是原问题最优目标值的一个下界。对于 最小化问题,BestInteger是所有最优整数解目标值中最小的一个,这个 目标值是原问题最优目标值的一个上界。对于最大化问题,在求解的过 程中,应该可以观察到BestInteger是非减的。 ·对于最大化问题,BestBound为当前已经求解到的最优非整数解的 目标值中最小的一个,这个目标值是原问题最优目标值的一个上界。对 于最小化问题,BestBound是所有最优非整数目标值中最大的一个,这 个目标值是原问题最优目标值的一个下界。对于最大化问题,可以观察 BestBound是非增的(如图23.10所示)。 ·Gap衡量的是BestInteger与BestBound之间的相对间隔。整个求解过 程实际上是不断(试图)缩小Gap的过程(如图23.10所示)。当Gap=0 时,上界和下界相等,求得最优整数解。在PROC OPTMODEL中,Gap 的默认取值为10,即如果当前Gap小于等于该默认值时,求解结束,当 前BestInteger对应的整数解即为原问题的最优解。Gap的具体计算公式 如下: ·在实际建模过程中,由于问题的规模一般比较大,如果要得到Gap 小于默认值的最优解往往要花费很多时间,而时间因素通常也是商业问 题求解过程中的一个重要考虑因素。因此,在实际操作中,当Gap足够 小时(例如0.5%),就可以近似地认为当前的BestInteger对应的整数解 为整数线性规划问题的最优解了。 表23.1总结了MILP求解器用于终止算法的各选项。 表23.1 终止MILP求解的选项及含义 调用表23.1中的选项的语法如下: SOVLE WITH MILP</选项>; 例如,以下语句将PROC OPTMODEL求解时间上限设置为600秒。 SOVLE WITH MILP/MAXTIME = 600; 例23.3:求解图23.1中的混合整数线性规划。 示例代码如下: proc optmodel; var x>=0 ; var y>=0 integer; con 2*x-y<=3; con 3*x+4*y<=9; max f=5*x+4*y; solve; print x y; quit; 运行以上代码,输出结果如图23.11所示。 图23.11 混合整数线性规划求解结果 23.3 使用0-1变量建模 在使用混合整数线性规划进行建模时,常见的一种情形是决策变量 的取值只能是0或1。例如,在建模中,一个常见的约束条件是成本约 束,成本仅发生在决策变量大于0时,即执行了某个操作才产生成本, 否则不产生成本。在这种情形下,往往需要使用0-1变量来辅助建模。 在PROC OPTMODEL中,可以使用关键字BINARY来声明0-1变量。例 如以下代码声明了一个以集合NODES为索引集的0-1变量组。 Var X{NODES} binary; 下面结合案例讲述如何使用0-1变量建模。 23.3.1 问题的提出 某商业银行在某市拥有大量的自动取款机。从银行的角度来看,在 不影响客户使用的前提下(即绝大多数来取现的客户都能够取到现 金),取款机内的现金越少越好。通过对每天取款数据的分析,银行已 经预测出未来7天中每天的取款需求,于是银行通过补钞车对取款机进 行了补钞。每台取款机由某一特定的补钞车进行补钞,且每次补钞都会 产生一定的费用。补钞车每天能够补给的取款机的数量是有一定限制 的。该商业银行希望结合预测数据,通过建模分析,制定一个未来7天 按天设计的最佳取款机补钞计划。 除了每台补钞车每天只能补给一定数量的取款机这一约束条件外, 本案例还有其他的一些约束条件,归纳如下: ·每台取款机能容纳的现金有一定上限。 ·每天补钞所用的现钞总额有一定限制。 ·周六、周日不进行补钞。 23.3.2 数学模型 1.索引集和索引集中的项 上述优化问题中需要用到的索引集包括以下这些。 ·ATM:集合ATM中的项为每台取款机机器的编号,a∈ATM,即a 的取值为取款机的编号。 ·DATE:集合DATE中的项为日期,d∈DATE,即d的取值为日 期。 ·LOCATION:集合LOCATION中的项为区域,∈LOCATION,的 取值为区域。 ·ATM_LOCATION:集合ATM_LOCATION中的项是取款机编号和 区域的组合,是二元组,(a,)∈ATM_LOCATION,a的取值为取款 机的编号,的取值为区域。 ·LOCATION_VTYPE:集合LOCATION_VTYPE中的项是区域和补 钞车的组合,是二元组,是一个二维集合,(,v) ∈LOCATION_VTYPE,第一个位置的数据表示的取款机所在的地区, 第二个位置的数据表示运钞车。 2.参数和参数数组 下面来看看上述优化问题中将会用到的参数情况,如表23.2所示。 表23.2 3.变量 取款机补钞问题中的参数 在了解了参数情况以后,再来看看会涉及哪些变量,如表23.3所 示。 表23.3 取款机补钞问题中的变量 4.目标函数 本例的目标是最小化成本。成本是由取款机内现钞的占用以及对取 款机补钞的费用组成的。在实际操作中,如果仅考虑这两部分成本,最 小化成本得出的最优解必然是补钞量为0,即不对取款机进行补钞。然 而,这种情形是不能满足银行服务客户的需要的。为此,需要在成本中 新增加一项:缺钞成本,即发生缺钞情况时,也会产生一定的费用。综 上,本问题的目标应为最小化总成本,其中总成本包含: ·补钞车的补钞成本。 ·取款机中现金占用产生的成本。 ·缺钞成本。 因此,目标函数为 其中,ReplenishCostWeight给补钞费用成本设立了一个权重, DailyIntrestRate表示现钞每天被占用的价格,CashShortCost表示单位缺 钞成本。这样,补钞过多时,就会发生补钞成本和现金占用成本;补钞 过少时,则会发生缺钞成本。3个成本相互制衡,也必然存在一个最优 的方案,使得3个成本之和达到最小。 5.约束条件 本问题的约束条件如下。 ·约束条件之一(InvPlusReplenish):每天补钞量加上前一天现金 余额的总和不能超过取款机可容纳的现金上限。如果当天为模型周期的 第一天,那么前一天现金余额为该取款机的初始现金额。因此,对于任 意的a∈ATM,d∈DATE,约束条件InvPlusReplenish可以表示如下: X[a,d]+(if d=&startdate.then init_inv[a,d]else sminus[a,d-1]) ≤capacity[a] ·约束条件之二(Slackvariable):记每天每台取款机上在d天结束 时预计的现金流为S,那么该现金流满足条件:S=当天的补钞量+前一 天的现金余额–预计当天的取钞金额。S的取值可以是正数或者非正数。 当S的取值为正数时,表明该天取款机上的现金余额大于0;反之,当S 的取值为负数时,表明该天取款机上发生了缺钞情况,缺钞金额为-S。 由前面决策变量Splus和决策变量Sminus的定义可知,S=Sminus–Splus。 因此,对于任意的a∈ATM,d∈DATE,有约束条件: Sminus[a,d]-Splus[a,d]=X[a,d]+(if d=&startdate.then init_inv[a,d]else sminus[a,d-1])-withdraw[a,d] ·约束条件之三(Replenish):对于发生补钞的取款机来说,0-1变 量IsReplenished的取值必须等于1。该约束条件可以通过决策变量X与参 数Capacity来表示。对于a∈ATM,d∈DATE,约束条件Replenish可以 表示成: X[a,d]<=capacity[a]*IsReplenished[a,d] ·从上述不等式看出,如果X[a,d]>0,由于参数Capacity[a]始终大 于0,因此,IsReplenished[a,d]的取值只能是1。 ·约束条件之四(DailyBudget):每天所有取款机的补钞总量不能 超出预算。对于任意的d∈DATE,有: ·约束条件之五(MaxNumReplenished):每台补钞车每天能补给的 取款机的台数有上限。对于任意的d∈DATE,∈LOCATION_VTYPE, 有: ·约束条件之六(SplusUB):每天缺钞的量不大于预计的取钞量。 对于任意的a∈ATM,d∈DATE,有: Splus[a,d]<=withdraw[a,d] ·约束条件之七(SminusUB):每天现金余额不会超过取款机能容 纳现金的上限。对于任意的a∈ATM,d∈DATE,有: Sminus[a,d]<=capacity[a] 上述的约束条件似乎比背景知识中提到的要多,这在实际中是常见 的。事实上,对于每一个定义的变量,都应该考虑其取值是否有上界或 者下界的约束。例如,上述约束条件Replenish、约束条件SplusUB以及 约束条件SminusUB分别对应值定义的决策变量IsReplenished、Splus以 及Sminus。 23.3.3 输入数据 表23.4汇总了ATM补钞问题建模过程中需要的全部数据。 表23.4 ATM补钞问题所需数据集信息 下面代码用于生成表23.4中的数据集。 dataatm; input atm_id $ location $ capacity cost; datalines; A001 L1 150000 400 A002 L1 150000 400 A003 L1 150000 800 A004 L1 100000 400 A005 L1 100000 800 A006 L1 100000 800 ; run; /*this data contains the amount of cash withdrawed per day per atm*/ data withdraw; input atm_id $ date mmddyy10. withdraw @@; format date date9.; datalines; A001 05/05/2014 21000 A002 05/05/2014 24000 A003 05/05/2014 21000 A004 05/05/2014 26000 A005 05/05/2014 19000 A006 05/05/2014 19000 A001 05/06/2014 30000 A002 05/06/2014 19000 A003 05/06/2014 27000 A004 05/06/2014 21000 A005 05/06/2014 21000 A006 05/06/2014 21000 A001 05/07/2014 27000 A002 05/07/2014 20000 A003 05/07/2014 26000 A004 05/07/2014 18000 A005 05/07/2014 22000 A006 05/07/2014 23000 A001 05/08/2014 20000 A002 05/08/2014 31000 A004 05/08/2014 20000 A005 05/08/2014 24000 A001 05/09/2014 31000 A002 05/09/2014 21000 A004 05/09/2014 19000 A005 05/09/2014 18000 A001 05/10/2014 30000 A002 05/10/2014 40000 A004 05/10/2014 26000 A005 05/10/2014 20000 A001 05/11/2014 35000 A002 05/11/2014 32000 A004 05/11/2014 15000 A005 05/11/2014 25000 ; run; data initial; input atm_id $ date mmddyy10. init_inv; datalines; A001 05/05/2014 0 A002 05/05/2014 0 A003 05/05/2014 0 A004 05/05/2014 0 A005 05/05/2014 0 A006 05/05/2014 0 ; run; data budget; input date mmddyy10. budget; format date date9.; datalines ; 05/05/2014 600000 05/06/2014 600000 05/07/2014 600000 05/08/2014 600000 05/09/2014 800000 05/10/2014 800000 05/11/2014 800000 ; run; data vehicle; input location $ vehicle_type $ count; datalines; L1 V1 4 ; run; 23.3.4 A003 A006 A003 A006 A003 A006 A003 A006 05/08/2014 05/08/2014 05/09/2014 05/09/2014 05/10/2014 05/10/2014 05/11/2014 05/11/2014 PROC OPTMODEL代码和输出 以下为模型的示例代码: %let DailyIntrestRate = %sysevalf(0.06/360); %let startdate = '05May2014'd; %let CashShortCost = 0.008; %let ReplenishCostWeight = 0.1; proc optmodel; /* define index sets*/ 20000 35000 30000 34000 55000 25000 35000 35000 set<str> ATM; set<num> DATE; set<str> LOCATION; set<str, str> ATM_LOCATION; set<str, str> LOCATION_VTYPE; /*define numrical variable to be read from data sets above*/ num capacity{ATM}; num cost{ATM}; num budget{DATE}; num withdraw{ATM, DATE}; num init_inv{ATM,DATE}; num count{LOCATION_VTYPE}; /*read data set*/ read data atm into ATM = [atm_id] capacity cost; read data atm into ATM_LOCATION = [atm_id location]; read data budget into DATE = [date] budget; read data withdraw into [atm_id date] withdraw; read data initial into [atm_id date] init_inv; read data vehicle into LOCATION_VTYPE = [location vehicle_type] count ; /*define decision variable*/ *X: the mount each atm replenished on a specific date; var X{ATM, DATE}>=0 integer; for {a in ATM} for {d in DATE} do; if weekday(d)=7 or weekday(d)=1 then fix X[a,d] = 0; *No replenish on saturday and sunday; end; var IsReplenished{ATM, DATE} binary; var splus{ATM, DATE}>=0 integer; var sminus{ATM, DATE}>=0 integer; /*ExtraCashCost: cost that results from storing capital in ATM*/ /*ReplenishCost: replenish cost each time*/ /*CashShortCost: penalty cost that result from cashout*/ impvar ExtraCashCost = sum{a in ATM, d in DATE}sminus[a, d]*&DailyIntrestRate. ; impvar ReplenishCost = sum{a in ATM, d in DATE}IsReplenished[a, d]*cost[a]; impvar CashShortCost = sum{a in ATM, d in DATE}splus[a, d]; /*define constraints*/ con InvPlusReplenish{a in ATM, d in DATE}: x[a, d] + (if d =&startdate. then init_inv[a, d] else sminus[a, d-1]) <= capacity[a]; con slackvariable{a in ATM, d in DATE}: x[a, d] + splus[a, d] - sminus[a, d] + (if d =&startdate. then 0 else sminus[a, d-1]) = withdraw[a, d]; con Replenish{a in ATM, d in DATE}: x[a, d] <= capacity[a]*IsReplenished[a, d]; con DailyBudget{d in DATE}: sum{a in ATM}x[a, d] <= budget[d]; con MaxNumReplenished{d in DATE, <l, v> in LOCATION_VTYPE}: sum{a in slice(<*, l> , ATM_LOCATION)}IsReplenished[a, d]<= count[l, v]; con SplusUB{a in ATM, d in DATE}: splus[a, d] <= withdraw[a, d]; con SminusUB{a in ATM, d in DATE}: sminus[a, d] <= capacity[a]; /*define objective function */ min TotalCost = ExtraCashCost + &ReplenishCostWeight.*ReplenishCost + &CashShortCost.*CashShortCost; /*call solver*/ solve; /*save results as data set*/ create data ReplenishPlan from [ATM DATE] Withdraw X quit; IsReplenished Splus Sminus; 运行上述代码,输出结果中的汇总信息如图23.12所示。 图23.12 取款机补钞模型求解汇总信息 生成的包含最优解的数据集work.Replenishplan如图23.13所示。例 如,方框中数据表示,编号为A001的取款机在DATE=19848(SAS内部 日期的存储形式)这一天的补钞量为98000元,当天期末的现金余额预 计为77000元。 图23.13 数据集work.Replenishplan 事实上,还可以对数据集work.Replenishplan进行进一步的加工。例 如,以下代码将每天的补钞量X和预计的缺钞量SPlus以二维矩阵的形式 表示出来(行表示取款机,列表示天)。 proc transpose data = ReplenishPlan out = DailyPlan name = ATM prefix = day; by ATM; var x; run; proc print data = DailyPlannoobs; title "每日补钞计划" ;run; proc transpose data = ReplenishPlan out = Splus name = ATM prefix = day; by ATM; varsplus; run; proc print data = Splusnoobs; title "变量Splus的取值"; run; 运行代码,输出结果如图23.14所示。从图中可以看出,该补钞计 划在周六以及周天没有发生补钞计划。 变量Splus表示缺钞的情况。从图23.15可以看出,在第一天,编号 为A005与A006的取款机会发生缺钞。其余情况下,都不会发生缺钞。 图23.14 图23.15 功能与技巧总结: 每日补钞计划 变量Splus的取值 对本案例中展示的PROC OPTMODEL的功能进行总结如下: ·确定问题类型为MILP(混合整数线性规划问题)。 ·对0-1变量进行定义并使用。 ·运用READ DATA语句读取多个数据集。 ·使用IMPVAR语句声明隐式变量。 ·在约束条件定义中,使用IF语句。 带绝对值的变量的处理,具体方法如下: 引进两个新的变量,使用新变量来代替原变量,以达到去除绝对值 的目的。两个新的变量分别代表原变量的正部与负部。假设变量为X, 正部与负部的符号分别记为X+和X-,二者的定义如下: X+=max{X,0} X-=max{-X,0} X与|X|二者可以通过正部与负部表示为如下形式: X=X+-X|X|=X++X- 在上例的建模中,实际上正是引入了两个新的变量来代表S的正部 与负部。 23.4 本章小结 本章介绍了常见的用于整数线性规划以及混合整数线性规划的方 法,即分支定界法与割平面法。分支定界法根据伴随问题的非整数最优 解对原问题进行分支定界,然后对分支进行求解以达到对原问题进行求 解的目的。割平面法通过割平面不断缩小可行域,达到求解的目的。 SAS中的MILP求解器使用了将两种方法进行结合的分支割平面法。 0-1变量是一类特殊的整数型变量,其取值只能为0或者1。在建模 过程中,0-1变量可以用来描述是否执行某个操作或者某个事件是否发 生。本章的取款机补钞问题分析了如何从商业问题出发,进行建模求 解,该过程涉及0-1变量的使用和带绝对值的变量的处理技巧等。 第24章 优化建模实例 本章将结合两个非常接近实际项目的案例,展示运用SAS/OR解决 实际优化问题的基本思路和方法:分析问题、建立数学模型、准备输入 数据、编写PROC OPTMODEL代码并分析输出结果。同时介绍PROC OPTMODEL中各种语句的用法和使用技巧。 24.1 24.1.1 集装箱问题 问题的提出 装箱问题(Bin Packing)是一个经典的组合优化问题,有着广泛的 应用,在日常生活中也屡见不鲜,比如集装箱的装箱问题。装箱问题可 分为一维装箱问题、二维装箱问题和三维装箱问题3种。一维装箱问题 只考虑一个因素,比如重量、体积或者长度等;二维装箱问题考虑两个 因素,比如给定一张矩形的纸,要求从这张纸上裁剪出给定的、大小不 一的形状,求一种裁剪方法使得裁剪剩下的废料面积的总和最小,常见 的问题包括对场地中(考虑长和宽)各功能区域的划分、停车场区位的 划分、包装材料的裁切等;三维装箱问题要考虑3个因素,比如长、 宽、高,在装车、装船或装集装箱时,要考虑这3个维度都不能超过给 定值。 这里要介绍的一个案例是来自某运输公司的集装箱问题。某运输公 司有一个码头,每周都有一定量的物品要装入集装箱中,然后从码头发 往别处。每个集装箱能容纳物品的最大总重量和最大总体积是固定的, 总重量不能超过4500公斤,总体积不能超过350立方英尺,但每件物品 的重量和体积各有差异。这时,集装箱的使用率需要从以下两个方面考 虑: ·每个集装箱的载重使用率=集装箱内物品的总重量/集装箱所能装载 的最大总重量 ·每个集装箱的体积使用率=集装箱内物品的总体积/集装箱所能装载 的最大总体积 该运输公司希望解决的问题如下: 1)从成本出发,确定用最小的集装箱数量来装载这批物品。 2)在确定最小的集装箱数量之后,确定最合理的装箱方案,使得 每个集装箱的载重使用率和体积使用率相对均衡。 24.1.2 数学模型 1.索引集和索引集中的项 该优化问题中需要用到的索引集如下。 ·ITEM:集合ITEM中的项为每件物品的ID,i∈ITEM,i代表物品 的ID。 ·CONTID:集合CONTID中的项为集装箱的ID,c∈CONTID,c代 表集装箱的ID。 ·ITEM_CONTID:集合ITEM_CONTID的项为二元组<i,c>,二元组第 一个位置的数据i代表物品的ID,第二个位置的数据c代表集装箱的ID。 2.参数和参数数组 下面是该问题需要用的参数情况,如表24.1所示。 表24.1 集装箱问题中的参数 3.变量 了解了参数后,再来看看问题中涉及的变量,如表24.2所示。 表24.2 4.目标函数 集装箱问题中的变量 该优化问题的第一个目标是最小化集装箱的使用数量。 第二个目标是在最小化集装箱数量的基础上,使得每个集装箱的载 重使用率和体积使用率尽可能均衡。W[c]和V[c]分别代表每个集装箱所 载物品的重量和体积与平均值的差异之最小上界,那么对于不同的装载 方法,如果某种装载方法使得每个集装箱与平均值的差异的最小上界之 和达到最小,那么该种装载方法就是最优的装载方法。因此优化目标和 目标函数如下: 5.约束条件 该优化问题必须满足如下约束条件: ·对于每一件物品i∈ITEM,只能装入一个集装箱中。 ·对于每一个集装箱c∈CONTID,所装载物品的重量可以表示为如 下形式: ·对于每一个集装箱c∈CONTID,所装载物品的体积可以表示为如 下形式: ·对于每一个集装箱c∈CONTID,所装载物品的重量不能超过装载 重量的上限。 WeightofContainer[c]≤Container[c]*weight_ub ·对于每一个集装箱c∈CONTID,所装载物品的体积不能超过装载 体积的上限。 CfeetofContainer[c]≤Container[c]*cubicfeet_ub ·对于每一个集装箱c∈CONTID,W[c]与所装载物品的重量和每个 集装箱平均重量有如下关系: W[c]≥| WeightofContainer[c]-avgweight| ·对于每一个集装箱c∈CONTID,V[c]与所载物品的体积和每个集 装箱平均体积有如下关系: V[c]≥|CfeetofContainer[c]-avgcfeet| 注意 参数avgweight和参数avgcfeet是通过PROC OPTMODEL的 优化结果计算所得的。后面会给出详细的信息。 24.1.3 输入数据 下列代码模拟了该优化模型中需要使用的输入数据集 work.shipped_items,该数据集中包含item_id(物品的ID)、 weight_kilo(物品的重量)和volume_cfeet(物品的体积)。 data work.data_shipped_items; do i=1 to 200; item_id="XITEM"||left(trim(_N_)); weight_kilo=input(ranuni(9999)*100,5.2); volume_cfeet=input(ranuni(999999)*75,5.2); output; end; run; 从第21章的介绍中得知,可以将非数组的参数数据存储在只包含一 条观测的数据集中,然后通过READ DATA语句给对应的参数赋值。其 实,这种数据也可以使用SAS宏变量来存储,每个宏变量中只存储一个 参数数据。本例中,使用宏变量weight_ub和cubicfeet_ub来存储集装箱 的载重上限和体积上限,如下: %let weight_ub=2000; %let cubicfeet_ub=1000; 24.1.4 PROC OPTMODEL代码和输出 在这个优化问题中有两个目标,第一个目标是求得最小集装箱数 量,第二个目标是建立在第一个目标实现的基础之上,求得尽量均衡的 装箱方案。PROC OPTMODEL中的求解器一次只能求解一个目标,那 么就从第一个目标入手。 首先,假设每件物品都装在不同的集装箱中,那么最多需要的集装 箱的数量就等于物品的总件数,将最多需要的集装箱个数存储在宏变量 item_cnt中。代码如下: proc sql; select count(item_id) into :item_cnt from work.data_shipped_items; quit; proc optmodel printlevel=1; set<str> ITEM; set<num> CONTID=1..&item_cnt; set ITEM_CONTID ={i in ITEM, c in CONTID}; num weight{ITEM} init 0; num cubicfeet{ITEM} init 0; read data data_shipped_items nomiss into ITEM=[item_id] weight=weight_kilo cubicfeet=volume_cfeet; 其中,索引集CONTID的项为1、2、…、&item_cnt。第一个READ DATA语句填充了集合ITEM,并将数据集中变量weight_kilo和 volume_cfeet分别赋值给了参数数组weight和cubicfeet。 选项PRINTLEVEL设定了PROC OPTMODEL的输出级别, PRINTLEVEL有3种取值,分别为0、1、2。默认情况下,该选项 PRINTLEVEL=1,在结果窗口将会输出问题汇总报表(Problem Summary)、优化结果报表(Solution Summary)和求解性能信息 (Performance Information)。PRINTLEVEL=0表示不输出任何报表。 接着,定义变量Container、ItemInContainer、WeightofContainer和 CfeetofContainer。由于变量WeightofContainer和CfeetofContainer分别可 以用变量ItemInContainer和参数数组组成的表达式表示,因此,可以将 变量WeightofContainer和CfeetofContainer定义成隐式变量,这样可以减 少优化问题中决策变量和约束条件的数量,提高求解效率。代码如下: var Container{CONTID} binary; var ItemInContainer{ITEM_CONTID} binary; impvar WeightOfContainer{c in CONTID} = sum{<i,(c)> in ITEM_CONTID} (ItemInContainer[i,c]*weight[i]); impvar CubicOfContainer{c in CONTID} = sum{<i,(c)> in ITEM_CONTID} (ItemInContainer[i,c]*cubicfeet[i]); 这里的代码“impvar WeightOfContainer{c in CONTID}=sum{<i, (c)>in ITEM_CONTID}(ItemInContainer[i,c]*weight[i]);”在SUM 集成表达式中使用了一种新的表达形式<i,(c)>,这段代码等价于如 下代码: impvar WeightOfContainer{c in CONTID} = sum{i in ITEM:<i,c> in ITEM_CONTID} (ItemInContainer[i,c]*weight[i]); 下面的语句用来声明约束条件和目标函数。 /*one item should only exist in only one container*/ con Oneitem_in_onecontainer{<i> in ITEM}: sum{<(i),c> in ITEM_CONTID} ItemInContainer[i,c]=1; /*the total weights in one container should not exceed the upper bound*/ con Container_weight_bound{c in CONTID}: WeightOfContainer[c]<=Container[c]*&weight_ub.; /*the total cubic feet in one container should not exceed the upper bound*/ con Container_cubicfeet_bound{c in CONTID}: CubicOfContainer[c]<=Container[c]*&cubicfeet_ub.; min MiniCounts=sum{c in CONTID}Container[c]; expand; 其中,EXPAND语句输出了上面定义的优化模型,如图24.1所示。 该语句是可选语句,主要用于程序调试,便于用户查看PROC OPTMODEL建立的模型是否和设想中的模型一致。 图24.1 EXPAND语句部分输出内容 因为决策变量ItemInContainer和Container是二分变量,因此SOLVE 语句将调用MILP求解器求解该问题。选项MAXTIME=600用于设定求 解时间的上限(求解时间由生成问题的时间和迭代求解时间两部分组 成),这里指定的时间上限是600秒。如果不指定时间上限,求解器将 不断迭代求解,直到求得最优解。在初次求解和调试MILP问题的时 候,建议使用选项MAXTIME=来设定时间上限,并根据日志的输出结 果来判断该问题求解的难易程度,再决定在最终的模型中是否设定运行 时间上限。代码如下: solve obj MiniCounts with milp /maxtime=600; 下面的CREATE DATA语句根据优化结果创建了两个输出数据集 work.item_in_container和work.container_kpi。{<i,c>in ITEM_CONTID:ItemInContainer[i,c]=1}表示只输出变量 ItemInContainer[i,c]=1的i和c的组合,并保存到数据集 work.item_in_container中。这里i和c都是虚拟参数,其值分别为物品的 ID和集装箱的ID。 {<c>in CONTID:Container[c]=1}表示仅将最优解中Container[c]=1 的集装箱的ID输出到数据集work.container_kpi中,同时输出每个集装箱 的总重量和总体积。代码如下: /*output result*/ create data work.item_in_container from [item_id container_id]={<i, c> in ITEM_CONTID: ItemInContainer[i,c]=1} weight_kilo=Weight[i] volume_cfeet=cubicfeet[i]; create data work.container_kpi from [container_id]={<c> in CONTID: Container[c]=1} Total_Weight=WeightOfContainer[c] Total_Cubic=CubicOfContainer[c]; quit; 上述PROC OPTMODEL代码执行后,结果窗口输出了该问题的问 题汇总报表、求解结果报表和求解性能报表,如图24.2所示。从问题汇 总报表中的Solution Status为Optimal可知,该问题已经求得最优解,也 表示MILP求解器在600秒之内已经迭代得到了最优解。 图24.2 例24.1PROC OPTMODEL输出内容 至此,该问题的第一个优化目标,即最小的集装箱数量已经求得, 为8个。 数据集work.item_in_container和数据集work.container_kpi内容如图 24.3所示。 图24.3 数据集work.item_in_container和work.container_kpi部分内容 每个集装箱的体积使用率基本比较均衡(除了第8个集装箱的容积 只使用了68.78cubicfeet),但每个集装箱的载重使用率差别比较大。接 下来要考虑的就是,如何装载可以使得这个8个集装箱的重量和容积使 用率尽量均衡。现在调用第二段PROC OPTMODEL程序求解第二个目 标下的优化问题。 首先,根据第一个优化问题的结果求得每个集装箱的平均装载重量 和平均装载体积,并保存在宏变量avgweight和avgcubicfeet中。代码如 下: proc sql; select avg(Total_Weight),avg(Total_Cubic) into :avgweight, :avgcubicfeet from container_kpi; quit; 接下来,声明第二个问题中需要使用的索引集、参数和参数数组。 需要注意的是,除了第一个问题中使用的索引集和参数数组外,还声明 了一个索引集INITIAL_ITEM_CONTID,用来保存第一次优化结果中物 品的装载方法。代码如下: proc optmodel; set<str> ITEM; set<num> CONTID; set<str,num> INITIAL_ITEM_CONTID; set ITEM_CONTID ={i in ITEM, c in CONTID}; num weight{ITEM} init 0; num cubicfeet{ITEM} init 0; num avgweight = &avgweight.; num avgcfeet = &avgcubicfeet.; 下面的语句声明了第二个问题中需要使用的决策变量和隐式变量。 和第一个问题不一样的是,第二个问题中没有声明变量Container,因为 从第一个问题的最优解中,已经得到集装箱的最小数量,并已确定哪些 集装箱被使用、哪些集装箱不需要使用了。这里另外声明了变量W和 V,分别用于衡量集装箱载重的体积的均衡性。 var ItemInContainer{ITEM_CONTID} binary; var W{CONTID}>=0; var V{CONTID}>=0; impvar WeightOfContainer{c in CONTID} = sum{<i,(c)> in ITEM_CONTID} (ItemInCont ainer[i,c]*weight[i]); impvar CubicOfContainer{c in CONTID} = sum{<i,(c)> in ITEM_CONTID} (ItemInConta iner[i,c]*cubicfeet[i]); 这里的约束条件Oneitem_in_onecontainer、Container_weight_bound 和Container_cubicfeet_bound和第一个问题中的基本一样,保证每件物品 都放入一个集装箱中,并且每个集装箱都不超过载重上限和体积上限。 代码如下: /*one item should only exist in only one container*/ con Oneitem_in_onecontainer{<i> in ITEM}: sum{<(i),c> in ITEM_CONTID} ItemInContainer[i,c]=1; /*the total weights in one container should not exceed the upper bound*/ con Container_weight_bound{c in CONTID}: WeightOfContainer[c]<=&weight_ub.; /*the total cubic feet in one container should not exceed the upper bound*/ con Container_cubicfeet_bound{c in CONTID}: CubicOfContainer[c]<=&cubicfeet_ub.; 决策变量W和V必须分别满足约束条件 W[contid]≥|WeightofContainer[contid]-avgweight|和 V[contid]≥|CfeetofContainer[contid]-avgcfeet|。这两个约束条件中都含有 绝对值符号,这属于非线性约束条件。一般来说,非线性规划比较难以 求解,但是可以通过一定的技巧,将这里的绝对值约束条件转化成线性 约束条件。 具体从以下两个方面考虑: ·当WeightofContainer[contid]-avgweight≥0时,约束条件为 W[contid]≥WeightofContainer[contid]-avgweight ·当WeightofContainer[contid]-avgweight≤0时,约束条件为 W[contid]≥avgweight-WeightofContainer[contid] 同理,约束条件V[contid]≥|CfeetofContainer[contid]-avgcfeet|也可以 转化成两个线性约束条件。代码如下: con con con con Lb_weight{c Ub_weight{c Lb_volume{c Ub_volume{c in in in in CONTID}: CONTID}: CONTID}: CONTID}: WeightOfContainer[c]-avgweight<=W[c]; avgweight-WeightOfContainer[c]<=W[c]; CubicOfContainer[c]-avgcfeet<=V[c]; avgcfeet-CubicOfContainer[c]<=V[c]; 第二个目标函数如下: min Variance=sum{c in CONTID} (W[c]+V[c]); 下面使用了3个READ DATA语句分别将原始数据集 work.data_shipped_items和第一个目标的优化结果work.container_kpi、 work.item_in_container读取进来。work.container_kpi中变量container_id 的值被赋给了索引集CONTID,work.item_in_container中的item_id和 container_id被联合赋给了集合INITIAL_ITEM_CONTID。 read data work.data_shipped_items nomiss into ITEM=[item_id] weight=weight_kilo cubicfeet=volume_cfeet; read data work.container_kpi nomiss into CONTID=[container_id]; read data work.item_in_container nomiss into INITIAL_ITEM_CONTID=[item_id container_id]; 下面的FOR语句为决策变量ItemInContainer、W和V都赋予了初 值。 /*assign initial solution*/ for {<i,c> in INITIAL_ITEM_CONTID} ItemInContainer[i,c]=1; for {c in CONTID} do; W[c]=&weight_ub.; V[c]=&cubicfeet_ub.; end; 在调用MILP求解器时,选项PRIMALIN使得MILP求解器可以使用 决策变量的当前值作为优化的初始解。如果决策变量的当前值不是当前 优化问题的可行解,MILP求解器将会帮助当前解自动修复,并将修复 后的解作为初始解。对于有些MILP问题,一组合适的初始整数解可以 大大降低MILP求解器的求解时间。使用选项PRIMALIN求解的代码如 下: solve with milp / primalin maxtime=400; /*output result*/ create data work.blance_item_in_container from [item_id container_id]= {<i, c> in ITEM_CONTID: ItemInContainer[i,c]=1} weight_kilo=Weight[i] volume_cfeet=cubicfeet[i]; create data work.balance_container_kpi from [container_id]={<c> in CONTID} Total_Weight=WeightOfContainer[c] Total_Cubic=CubicOfContainer[c]; quit; 将MILP求解时间设为400秒后,每个集装箱的载量和体积被存储到 work.balance_container_kpi中,数据集内容如图24.4所示。和图24.3相比 较,每个箱子的装载重量和体积的均衡性都有了很大的改进。 图24.4 数据集work.balance_container_kpi的内容 需要指出的是,第二个问题在MILP求解器运行400秒后,并没有达 到最优解。由于运行时间已经达到400秒,系统将当前解作为最优解输 出。同时日志信息和结果窗口都输出到达运行时间上限的信息,如图 24.5所示。 图24.5 运行时间达到上限时的日志信息和输出结果 如果将选项MAXTIME=设定的时间增大,当前解应该可以得到进 一步改善。 在这个例子中,先后两次调用了PROC OPTMODEL,并且注意到 两个模型中的绝大部分索引集和参数数组都相同,只是决策变量和约束 条件不尽相同。在这种情况下,除了调用多段PROC OPTMODEL程序 分别建立模型以外,也可以在同一个PROC OPTMODEL程序中使用 PROBLEM语句来申明不同的子模型,然后运用USE PROBLEM语句调 用不同的子模型分别求解。在不同的子模型中,可以共用共同的索引集 和参数。读者可以查看SAS帮助文档了解PROBLEM语句和USE PROBLEM语句的具体语法。 24.1.5 功能与技巧汇总 该优化问题是一个混合整数线性规划问题,针对该实例,用到了 PROC OPTMODEL中如下的功能和技巧: ·声明数据类型为数值型、字符型和二元组的索引集。 ·运用READ DATA语句读取多个数据集。 ·运用IMPVAR语句声明隐式变量。 ·运用EXPAND语句展示模型。 ·使用(:)进行索引集中项的筛选。 ·线性化含绝对值符号的约束条件。 ·调用MILP求解器。 ·使用选项MAXTIME=设定运行时间。 ·运用CREATE DATA语句创建多个数据集。 ·使用选项PRIMALIN使得MILP求解器利用初始值进行求解。 24.2 24.2.1 运输排程问题 问题的提出 某汽车制造商有一个生产基地,每天按一定的生产计划生产各种配 置、各种颜色的汽车,并根据一定的调度计划通过船舶、火车或者公路 运往9个地区仓库(WH1~WH9)。运输网络如图24.6所示。 图24.6 某汽车制造商的运输网络 由于码头和火车站的容量有限,以及调度安排的高复杂性,很多从 生产线下来的汽车并不能够第一时间被安排运往码头或者火车站,只能 先存放到生产基地的仓库中,供后续调度。为了使得生产线能够持续运 行,生产基地仓库往往需要设定比较大的库容,以容纳从生产线下来而 没有被安排运往码头或者火车站的汽车。 通过长期的运营,该公司意识到当前的调度计划存在以下问题: ·由于调度计划不够完善,基地仓库必须长期保持较大的库容。每 辆汽车在基地仓库的固定存放成本很大,占库存成本的80%以上。因为 不管存放时间是多少(1个小时、2个小时或者1天、2天),每辆从生产 线下来的汽车都会在基地仓库中发生一笔固定的存放成本。因此,如果 可以减少进库汽车的数量,特别是减少短时间停放的进库汽车数量,将 会为公司节约巨大的成本。 ·运往码头或者火车站的汽车呈现出两种分化现象,一方面,不少 汽车在码头或者火车站停放的时间较长,最长的甚至超过4天,才能装 船或者装车运走;另一方面,不少船舶或者火车在出发的时候,该公司 购买的运能并没有被充分利用,造成了运输资源的浪费。 鉴于此,该公司希望结合生产计划、船舶排程和运能计划、火车排 程和运能计划,制定一个科学的运输调度计划,来实现基地仓库零库存 的目标,同时减少运输资源的浪费现象。在这个调度计划中,需要确定 按每种运输方式,每天调度给每个地区仓库的配置不同且颜色各异的汽 车数量。 汽车在调度给某地区仓库后,如果采用船运的方式,那么将直接运 往码头等待发往该地区仓库的船舶装船出发;如果采用火车运输方式, 那么将直接运往火车站等待发往该地区仓库的火车装车出发。 船舶和火车的运能比较大,通常当天生产下线的产量可能不一定能 够将船舶或者火车的运能充分利用。因此,公司同意船舶和火车存在集 单的过程,即调度运往码头或者火车站的汽车不需要立即装船或者装车 出发,可以在码头或者火车站待运,在规定的天数内出发即可。因此, 调度计划和实际运输之间存在一定的时间差。例如,在第T天调度的通 过船舶运往某地区仓库的汽车,在第T天从生产线运到码头后,可能在 第T+1天才真正从码头出发运往地区仓库。 此外,由于船运和火车的运能有限,该公司还安排了公路运输方 式,在前两种运输方式运能不够的情况下,可以通过公路发往地区仓 库。公路运输没有固定的排程和运能约束,每天都可以发运。从成本角 度出发,应优先安排船运和火车运输,公路运输最后才考虑。 由于生产计划是固定的,船舶的运能、排程和火车的运能、排程可 以提前9天知道,该公司希望制定周度调度计划,即每周一早晨制定本 周中每天的调度计划。 以2014年7月的第二周为例,该公司计划生产2种配置的汽车,每种 配置的汽车又分不同颜色,每天的生产计划如表24.3所示。 表24.3 生产计划示例 基于生产计划和需求,市场部门提前一周计算出了下一周中每个地 区仓库各种配置及颜色的汽车的发运量和紧急程度,数据如表24.4所 示。紧急程度用1~10的数字表示,数字越大表示该地区仓库对这种配置 颜色的汽车需求的紧急程度越高。在安排调度的时候,紧急程度高的地 区仓库应优先安排调度。 表24.4 地区仓库发运量和紧急程度示例 另外,从生产基地到9个地区仓库的运输方式不完全一样,有的线 路上只有船运和公路运输,有的线路只有火车和公路运输,有的线路是 船运、火车和公路运输3种方式并存。在每条线路上,优先使用船运和 火车运输,公路其次。 各条线路上,每周船运的容量和出发日期都是确定的,如表24.5所 示。 表24.5 船舶运能和排程(非合并线路) 对于船运来说,除了上面的非合并运输线路之外,地区仓库WH2和 WH3,地区仓库WH4和WH5还有另外一部分共用的船舶运能,例如发 运往WH2和WH3的汽车可以装载在同一艘船舶上,船舶在途经WH2 时,卸载一部分车辆,然后继续行驶到WH3。这部分线路称为合并线 路,合并线路的船舶运能和排程如表24.6所示。 表24.6 船舶运能和排程(合并线路) 火车运能和排程如表24.7所示,火车运输不存在合并线路的情况。 表24.7 火车运能和排程 在根据调度计划调度后,如果指定的运输方式是船运或者火车运 输,车辆可以停放在码头或者火车站的临时仓库中待运,为了避免出现 前面提到的部分车辆在码头或者火车站滞留4到5天的情况,公司要求在 制定新的调度计划时,规定每辆汽车在码头或者火车站最多只能停留两 天,也就是说,在第T天调度到码头或者火车站的车辆,必须在第T 天、第T+1天或者第T+2天从码头或者火车站出发运往各地区仓库。 总结下来,新的调度计划必须满足以下目标(按优先级从高到低 排): ·实现生产基地仓库零库存,即每天生产下线的汽车,必须被调度 往码头、火车站或者通过公路运输直接发往各地区仓库,不能在生产基 地逗留。 ·调度到码头或者火车站的汽车在码头或者火车站最多只能停留两 天。 ·在安排运输方式时,从成本考虑,优先船运和火车(不分顺 序),其次考虑公路运输。 ·紧急程度高的地区优先安排船运和火车运输(因为这两种运输方 式比较稳定)。 24.2.2 数学模型 1.索引集和索引集中的项 该优化问题中需要用到的索引集包括以下这些。 ·DOW:集合中的项为Mon、Tue、Wed、Thu、Fri、Sat、Sun, d∈DOW,即d的取值为Mon或者Tue等。 ·DOW_EXT:集合中的项为Mon、Tue、Wed、Thu、Fri、Sat、 Sun、NextMon、NextTue,d∈DOW_EXT,即d的取值为Mon或者Tue 等。 ·PACKAGE_COLOR:集合中的项为汽车的配置和颜色组合, (p,c)∈PACKAGE_COLOR,p的取值为汽车的配置,c的取值为汽 车的颜色。 ·WAREHOUSE:地区仓库的集合,w∈WAREHOUSE,w的取值 为地区仓库的名称,如WH1或者WH2等。 ·WH_PACKAGE_COLOR:发往地区仓库w的汽车配置和颜色的组 合的集合,(w,p,c)∈WH_PACKAGE_COLOR,w的取值为地区仓 库的名称,p的取值为汽车的配置,c的取值为汽车的颜色。 ·TRAINROUTE:集合TRAINROUTE中的项为含有火车运输方式的 地区仓库名称,w∈TRAINROUTE,w的取值为地区仓库的名称,如 WH4、WH5等。 ·SHIPROUTE:集合SHIPROUTE中的项为含有船运方式的地区仓 库名称,w∈SHIPROUTE,w的取值为地区仓库的名称,如WH1、 WH2等。 ·SHAREROUTE:集合SHAREROUTE中的项由合并船运的地区仓 库名称合成,s∈SHAREROUTE,s的取值为WH2WH3或者WH4WH5。 注意 索引集DOW_EXT和DOW的区别在于DOW_EXT中比 DOW多了两个项NextMon、NextTue,这是因为生产计划和调度计划的 时间窗口是一周(从周一到周日),然而根据调度计划来看,调度的汽 车在码头或者火车站可以停留0到2天再出发,故实际运输的时间窗口需 要后延两天,为周一到下周二。因此,调度计划以DOW为索引集,而 实际运输以DOW_EXT为索引集。 2.参数和参数数组 在该优化问题中需要用到的参数情况如表24.8所示。 表24.8 运输排程问题中的参数 3.变量 在了解了参数情况后,再来看看所涉及的变量,如表24.9所示。 表24.9 运输排程问题中的变量 在本问题中,由于调度计划和实际运输时间之间存在一定的时间 差,调度到码头或者火车站的汽车一般不可能当天就从码头或者火车站 出发运往各地(公路运输不存在滞后,当天调度的汽车当天就可以出发 运往各地),因此,在模型中,设计了两套变量,其中以Alloc_开头的 是代表调度计划的变量,不以Alloc_开头的变量如Train、Ship、 Ship_direct、Ship_shared、Highway、Delivery代表的是和实际运输关联 的变量。 4.目标函数 该优化问题的目标不同于一般的优化问题,例如,成本最小化,利 润最大化。在24.2.1节的最后,总结了该问题有4个目标,通过分析得 知,其中前两个目标是硬性目标(基地仓库的零库存目标和在码头或者 火车站停留不能超过两天),调度计划必须完全服从和满足,因此将这 两个目标转化成了约束条件。此外,根据第三个目标(优先船运和火车 运输),可构造以下目标函数: 在目标函数中将变量Slack_train[w,d]、Slack_ship_direct[w,d]和 Slack_ship_shared[s,d]按天、按地区仓库进行了相加,并且最小化该目 标函数。这里的(10-Index[d])是每天剩余运能的权重,表示在每周 中,前面几天的权重相对较大,也就是浪费前几天的运能所得到的惩罚 会更加大。在这个新的目标函数下,最优解一定要在满足约束条件的情 况下,充分使用船舶或者火车的运能。 另一个要考虑的目标是,根据各地区仓库需求的紧急程度,相同条 件下(例如,地区仓库WH1的需求量为10,地区仓库WH2的需求量也 为10,并且船期也相同,船舶的运能也相同),紧急程度高的地区优先 安排调度,可以构造目标函数: 在上述目标函数中,bklg[w,p,c]和(10-index[d])的积作为每天 的运输量(Train[w,p,c,d]+Ship[w,p,c,d])的权重,在这个目 标函数下,紧急程度越高的地区仓库将越早安排运输。目标函数Priority 的最后一项是上一个目标函数Penalty,这是因为在考虑第四个目标的时 候,我们希望同时兼顾第三个目标。和24.1节中的案例相比较,这是另 一种处理多个目标函数的方法。 5.约束条件 该优化问题有如下约束条件。 ·对于任意的(w,p,c)∈WH_PACKAGE_COLOR,通过船舶和 火车运输的总量和计划的总发运量具有如下关系(因为公路运输的数量 不包含在船舶和火车运输的总量中): ·对任意的(w,p,c)∈WH_PACKAGE_COLOR,计划的总发运 量和调度的总量必须相等。 ·对于任意的w∈TRAINROUTE,d∈DOW_EXT,有: ·对于任意的w∈SHIPROUTE,d∈DOW_EXT,有: ·对于任意的s∈SHAREROUTE,d∈DOW_EXT,有: ·对于任意的(p,c)∈PACKAGE_COLOR,d∈DOW_EXT,通 过船舶和火车发运的累计总量小于等于生产总量与期初库存累计之和。 ·对于任意的(p,c)∈PACKAGE_COLOR,d∈DOW_EXT,累 计的调度总量小于等于生产总量与期初库存累计之和。 ·为了实现基地仓库的零库存目标,即每辆生产线下来的汽车都不 进基地仓库,所以每天的生产量必须每天都安排调度,故,对于任意的 (p,c)∈PACKAGE_COLOR,d∈DOW,有: ·对于火车运输或者船运,对于任意的(p,c) ∈PACKAGE_COLOR,d∈DOW,当index[d]<7时,累计的调度总量大 于等于实际运输的总量。 ·对于火车运输或者船运,对于任意的(p,c) ∈PACKAGE_COLOR,d∈DOW,当index[d]=7时,累计的调度总量大 于等于实际运输的总量。 ·在第T天调度到码头和火车站的汽车,必须在第T天到T+2天之间 从码头或者火车站出发运往各地区仓库。以火车站为例,假设周一调度 往火车站的汽车数量为Alloc_train[w,p,c,1],周一出发的数量为 Train[w,p,c,1],则周一剩余在火车站的临时库存为Alloc_train[w, p,c,1]-Train[w,p,c,1],按照要求,这部分库存必须在周二和周三 全部运出。记周二和周三出发的数量分别为Train[w,p,c,2]和 Train[w,p,c,3],因此,有Alloc_train[w,p,c,1]-Train[w,p,c, 1]≤Train[w,p,c,2]+Train[w,p,c,3],即Alloc_train[w,p,c, 1]≤Train[w,p,c,1]+Train[w,p,c,2]+Train[w,p,c,3]。记周二 调度往火车站的汽车数量为Alloc_train[w,p,c,2],则周二剩余在火 车站的库存为Alloc_train[w,p,c,1]-Train[w,p,c, 1]+Alloc_train[w,p,c,2]-Train[w,p,c,2],按照要求这部分库存必 须最迟在周四全部运出,因此,有Alloc_train[w,p,c,1]-Train[w, p,c,1]+Alloc_train[w,p,c,2]-Train[w,p,c,2]≤Train[w,p,c, 3]+Train[w,p,c,4],即Alloc_train[w,p,c,1]+Alloc_train[w,p, c,2]≤Train[w,p,c,1]+Train[w,p,c,2]+Train[w,p,c, 3]+Train[w,p,c,4]。从上面的推导中,可以看出,该约束条件可以 转化为,对于任意的(w,p,c)∈WH_PACKAGE_COLOR, d∈DOW,有: ·对任意的(w,p,c)∈WH_PACKAGE_COLOR, d∈DOW_EXT,有: Train[w,p,c,d]=10*Train_cart[w,p,c,d] ·对任意的(w,p,c)∈WH_PACKAGE_COLOR,d∈DOW, 有: Alloc[w,p,c,d]=Alloc_ship[w,p,c,d]+Alloc_train[w,p,c, d]+Alloc_highway[w,p,c,d] ·对任意的(w,p,c)∈WH_PACKAGE_COLOR, d∈DOW_EXT,有: Ship[w,p,c,d]=Ship_direct[w,p,c,d]+Ship_shared[w,p,c, d] ·对任意的(w,p,c)∈WH_PACKAGE_COLOR, d∈DOW_EXT,有: Delivery[w,p,c,d]=Ship[w,p,c,d]+Train[w,p,c,d] ·对任意的(w,p,c)∈WH_PACKAGE_COLOR, 当d∈DOW,Highway[w,p,c,d]=Alloc_highway[w,p,c,d] 当d∈DOW_EXT\DOW,Highway[w,p,c,d]=0 24.2.3 输入数据 该优化模型中需要使用的输入数据集如表24.10所示。 表24.10 输入数据集信息 下列代码创建了以上数据集: data work.allocation; length warehouse $3 package input warehouse $ package $ datalines; WH1 EXCELLE BLACK 300 WH2 EXCELLE BLACK 100 WH3 EXCELLE BLACK 80 WH4 EXCELLE BLACK 400 WH5 EXCELLE BLACK 500 WH6 EXCELLE BLACK 250 WH7 EXCELLE BLACK 460 WH8 EXCELLE BLACK 320 WH9 EXCELLE BLACK 540 WH1 EXCELLE GREY 600 WH2 EXCELLE GREY 300 WH3 EXCELLE GREY 720 WH4 EXCELLE GREY 300 WH5 EXCELLE GREY 600 WH6 EXCELLE GREY 200 WH7 EXCELLE GREY 700 WH8 EXCELLE GREY 400 WH9 EXCELLE GREY 730 WH1 TENNAL RED 40 WH2 TENNAL RED 30 WH3 TENNAL RED 40 WH4 TENNAL RED 70 WH5 TENNAL RED 45 WH6 TENNAL RED 57 WH7 TENNAL RED 25 WH8 TENNAL RED 53 WH1 TENNAL BLACK 112 WH2 TENNAL BLACK 300 WH3 TENNAL BLACK 230 $8; color $ allocation bklg; 7 6 3 1 2 7 4 9 6 7 4 9 8 3 5 2 1 7 6 10 9 8 6 8 4 6 1 2 7 WH4 TENNAL BLACK 120 4 WH5 TENNAL BLACK 340 8 WH6 TENNAL BLACK 200 6 WH7 TENNAL BLACK 75 10 WH8 TENNAL BLACK 125 3 WH9 TENNAL BLACK 190 6 WH1 TENNAL GREY 120 4 WH2 TENNAL GREY 320 1 WH3 TENNAL GREY 230 6 WH4 TENNAL GREY 100 5 WH5 TENNAL GREY 450 6 WH6 TENNAL GREY 230 4 WH7 TENNAL GREY 222 4 WH8 TENNAL GREY 72 1 WH9 TENNAL GREY 156 10 ; run; data work.production; input package $ color $ weekday $ quantity dow; datalines; TENNAL RED Mon 70 1 TENNAL RED Tue 80 2 TENNAL RED Wed 90 3 TENNAL RED Thu 100 4 TENNAL BLACK Mon 300 1 TENNAL BLACK Tue 340 2 TENNAL BLACK Wed 340 3 TENNAL BLACK Thu 350 4 TENNAL BLACK Fri 350 5 EXCELLE BLACK Mon 400 1 EXCELLE BLACK Tue 450 2 EXCELLE BLACK Wed 500 3 EXCELLE BLACK Thu 500 4 EXCELLE BLACK Fri 500 5 EXCELLE BLACK Sat 300 6 EXCELLE BLACK Sun 300 7 EXCELLE GREY Mon 600 1 EXCELLE GREY Tue 600 2 EXCELLE GREY Wed 650 3 EXCELLE GREY Thu 600 4 EXCELLE GREY Fri 600 5 EXCELLE GREY Sat 700 6 EXCELLE GREY Sun 600 7 TENNAL GREY Mon 500 1 TENNAL GREY Tue 450 2 TENNAL GREY Wed 450 3 TENNAL GREY Thu 500 4 ; run; data work.stock; input package $ color $ stock; datalines; EXCELLE GREY 200 TENNAL RED 20 TENNAL BLACK 12 ; run; data work.ship_direct_route; length warehouse $3 shared_route $6; infile datalines dsd missover; input warehouse $ shared_route $ Mon Tue Wed Thu Fri Sat Sun NextMon NextTue; datalines; WH1,DIRECT,,350,,200,300,,,200 WH2,WH2WH3,,300,,150,,150 WH3,WH2WH3,,350,,,350,,350 WH4,WH4WH5 WH5,WH4WH5,300,,300,,,300,,,300,,,300 WH7,DIRECT,330,230,,230,,230,,330 WH8,DIRECT,230,,,230,,,230 ; run; data work.ship_shared_route; length shared_route $6; infile datalines dsd missover; input shared_route $ Mon Tue Wed Thu Fri Sat Sun NextMon NextTue; datalines; WH2WH3,,250,,150,,,250 WH4WH5,,150,,,,250,,,150 ; run; 在船运排程中,为了使得合并运输线路与非合并运输线路相互关联 起来,在设计数据模型时,数据集work.ship_direct_route中增加了变量 shared_route。如果在去往某地区仓库的线路中不存在合并运输线路,则 shared_route的取值为DIRECT,如地区仓库WH1这一行中,shared_route 的取值为DIRECT;如果存在合并运输线路,则shared_route的取值为合 并运输线路的名称,如地区仓库WH5这一行中,shared_route的取值为 WH4WH5;如果仅存在合并运输线路,则在数据集中 work.ship_direct_route添加一行虚拟的非合并运输线路,以便仅对变量 warehouse和shared_route赋值,例如地区仓库WH4这一行,运能数据全 部缺失,表示去往地区仓库WH4的线路中,并不存在非合并运输线路, 但是shared_route的取值为WH4WH5,表示去往WH4的线路中,存在一 条合并运输线路。 data work.train_schedule; length warehouse $3; infile datalines dsd missover; input warehouse $ Mon Tue Wed Thu Fri Sat Sun NextMon NextTue; datalines; WH4,150,,,150,,,,150 WH5,,150,,,,290,,,150 WH6,290,,,,290,,,290 WH8,,150,,,150,,,150 ; run; data work.stock_at_port; length mode $5 warehouse $3; input mode warehouse stock_at_port; datalines; ship WH1 10 ship WH5 20 ship WH7 14 ship WH8 15 train WH4 20 train WH6 18 ; run; 在work.stock_at_port中保存的是周初在码头和火车站待运的汽车数 量。这部分汽车数量属于上周调度计划中的部分,由于允许有0~2天的 待运时间,故它们仍然在码头和火车站待运。需要注意的是,这部分数 量会占用本周船舶和火车的运能。 24.2.4 数据验证 输入数据是优化模型的基础,为了避免在模型运行过程中出现不可 预测的错误,所有的输入数据都必须先经过验证,然后再输入模型中。 验证的内容包括数据的合理性、数据集之间的相互关联性、数据的完整 性等内容。 前面的实例中使用了多个输入数据集,并且数据集之间是相互关联 的,因此本例中运用了DATA步、HASH对象和SQL过程对多个数据集 进行了验证。 在实际项目中,数据集是通过ETL程序从生产环境或其他数据库中 抽取并加工生成的,这样一来,数据的合理性、关联性和完整性往往不 能得到保证。因此,在实际项目中数据验证这一步骤更加重要。 首先,为了避免改变原数据集,将所有的数据都复制到临时库的新 数据集中。代码如下: *** copy data into work ***; data work.data_allocation;set work.allocation;run; data work.data_production;set work.production;run; data work.data_stock;set work.stock;run; data work.data_train_schedule; set work.Train_schedule; run; data work.data_ship_direct_route; set work.Ship_direct_route; run; data work.data_ship_shared_route; set work.Ship_shared_route; run; data work.data_stock_at_port; set work.stock_at_port; mode=upcase(mode); run; proc transpose data=work.data_train_schedule(drop=warehouse) out= work.day_of_week_ext(keep=_name_); run; data work.day_of_week_ext; set work.day_of_week_ext; label _name_='day of week extention'; index=_n_; run; 接下来在宏DataValidation中对数据集进行验证,将错误的数据从原 数据集中删除,并输出到对应的数据集work.error_XXX中(同时输出错 误原因)。代码如下: ***Data validation****; %macro DataValidation; ***validate allocation table and only keep set <package color> with positive allocations and nonnegative bklg ***; data work.exp_allocation(keep=package color error) work.data_allocation(drop=error); set work.data_allocation; if allocation <= 0 or bklg <0 then do; error = 'nonpositive allocation or negative backlog'; output work.exp_allocation; end; else output work.data_allocation; run; 以上程序将数据集work.data_allocation中变量allocation或者bklg的取 值小于0的观测从work.data_allocation中删除,并将其保存到 work.exp_allocation中。 data work.temp_allocation(keep=package color); set work.data_allocation; run; proc sort nodup data=work.temp_allocation out=work.val_package_color; by package color; run; proc sql; select count(*) into :exp_allocation from work.exp_allocation; quit; %put exp=&exp_allocation; *** validate production/stock table and create data_production_stock table ***; data work.exp_production work.data_production(drop=error); length package $8 color $8; if _n_=1 then do; declare hash h(dataset:'work.val_package_color'); h.definekey('package','color'); h.definedone(); call missing(package,color); end; set work.data_production; if h.find()^=0 then do; error = 'no such (package,color) in allocation table'; output work.exp_production; end; else if quantity<0 then do; error = 'negative production quantity'; output work.exp_production; end; else output work.data_production; run; proc sql; select count(*) into :exp_production from work.exp_production; quit; 以上程序将数据集work.data_production中与work.data_allocation中 的package和color不匹配的观测或者quantity取值不合理的观测从数据集 work.data_production中删除,并将删除的观测保存到数据集 work.exp_production中。 data work.exp_stock work.data_stock(drop=error); length package $8 color $8; if _n_=1 then do; declare hash h(dataset:'work.val_package_color'); h.definekey('package','color'); h.definedone(); call missing(package,color); end; set work.data_stock; if h.find()^=0 then do; error = 'no such (package,color) in allocation table'; output work.exp_stock; end; else if stock<0 then do; error = 'negative stock value'; output work.exp_stock; end; else output work.data_stock; run; proc sql; select count(*) into :exp_stock from work.exp_stock; quit; 以上程序将数据集work.data_stock中与work.data_allocation中的 package和color不匹配的观测或者stock取值不合理的观测从数据集 work.data_stock中删除,并将删除的观测保存到数据集work.exp_stock 中。 data work.temp_production; set work.val_package_color; run; %do i=1 %to 7; proc sql; create table work.temp_production_1 as selecta.*, %if &i=1 %then b.quantity as Mon; %else %if &i=2 %then b.quantity as Tue; %else %if &i=3 %then b.quantity as Wed; %else %if &i=4 %then b.quantity as Thu; %else %if &i=5 %then b.quantity as Fri; %else %if &i=6 %then b.quantity as Sat; %else b.quantity as Sun; from work.temp_production as a left join work.data_production (where=(dow=&i)) as b on a.package = b.package and a.color = b.color; quit; data work.temp_production; set work.temp_production_1; run; %end; proc sql; create table work.data_production_stock as selecta.*, stock from work.temp_production as a left join work.data_stock as b on a.package = b.package and a.color = b.color; quit; 以上程序根据work.data_production中的生产数据和work.data_stock 中的初始库存数据,生成了新的数据集work.data_production_stock。新 数据集中观测关于变量package和color是唯一的,每天的产量和初始库 存将保存在不同的列中。 接下来的代码会对船舶、火车的运输线路进行验证,原理和方法同 上面对生产及库存数据的验证。这里以work.data_allocation中的 wareshouse为基准,将在本次优化中不需要的线路从运能和排程数据集 中删除,并保存到对应的以exp_开头的数据集中。 *** Only consider route with allocation defined ***; data work.val_route(keep=warehouse); set work.data_allocation; run; proc sort nodup data=work.val_route; by warehouse; run; data work.exp_ship_direct_route(keep=warehouse error) work.data_ship_direct_route(drop=error); length warehouse $3; if _n_=1 then do; declare hash route(dataset:'work.val_route'); route.definekey('warehouse'); route.definedata('warehouse'); route.definedone(); call missing(warehouse); end; set work.data_ship_direct_route; if route.find()^=0 then do; error = 'no such route in allocation table'; output work.exp_ship_direct_route; end; else output work.data_ship_direct_route; run; proc sql; select count(*) into :exp_ship_direct_route from work.exp_ship_direct_route; quit; data work.exp_train_schedule(keep=warehouse error) work.data_train_schedule(drop=error); length warehouse $3; if _n_=1 then do; declare hash route(dataset:'work.val_route'); route.definekey('warehouse'); route.definedata('warehouse'); route.definedone(); call missing(warehouse); end; set data_train_schedule; if route.find()^=0 then do; error = 'no such route in allocation table'; output work.exp_train_schedule; end; else output work.data_train_schedule; run; proc sql; select count(*) into :exp_train_schedule from work.exp_train_schedule; quit; 下面的代码会对码头和火车站的期初库存数据进行验证。 *** Only consider stock with known route ***; data work.exp_stock_at_port(keep=warehouse error) work.data_stock_at_port(drop=error); length warehouse $3; if _n_=1 then do; declare hash ship(dataset:'work.Data_ship_direct_route'); ship.definekey('warehouse'); ship.definedata('warehouse'); ship.definedone(); call missing(warehouse); declare hash train(dataset:'work.Data_train_schedule'); train.definekey('warehouse'); train.definedata('warehouse'); train.definedone(); call missing(warehouse); end; set work.data_stock_at_port; if mode='TRAIN' and train.find()^=0 then do; error = 'no such route in train schedule'; output work.exp_stock_at_port; end; else if mode='SHIP' and ship.find()^=0 then do; error = 'no such route in ship route'; output work.exp_stock_at_port; end; else output work.data_stock_at_port; run; proc sql; select count(*) into :exp_stock_at_port from work.exp_stock_at_port; quit; 在将上面所有的数据集都验证完毕之后,将会生成一个关于所删除 观测的汇总信息数据集,其中每条观测为每个数据集中删除的观测条 数。通过汇总数据集提供的信息,用户可以通过查看对应的以exp_开头 的数据集,具体分析产生数据错误的原因。 *** aggregate exceptions into exp_status table ***; proc sql; create table work.exp_status ( table char 32 label='table name', remove num label='removed records' ); insert into work.exp_status values('data_allocation',&exp_allocation) values('data_production',&exp_production) values('data_stock',&exp_stock) values('data_ship_direct_route',&exp_ship_direct_route) values('data_train_schedule',&exp_train_schedule) values('data_stock_at_port',&exp_stock_at_port); quit; proc datasets library=work NOPRINT; delete temp_: val_:; quit; %mend DataValidation; %DataValidation; 原数据集中所有被删除的观测条数汇总如图24.7所示。 图24.7 数据集Work.Exp_status内容 图24.7表明24.2.3节中的代码创建的数据集是合理的。 在宏DataValidation的最后,通过DATASETS过程删除了验证过程 中创建的、数据集名称以temp_或val_开头的中间数据集。 24.2.5 PROC OPTMODEL代码和输出 以下为调用PROC OPTMODEL建立模型的示例代码: proc optmodel; set<str> DOW_EXT; num index{DOW_EXT}; read data work.day_of_week_ext into DOW_EXT=[_name_] index; set<str> DOW = {d in DOW_EXT: index[d]<=7}; 在PROC OPTMODEL的最开始,创建了两个索引集DOW_EXT和 DOW,DOW_EXT和DOW中的项分别为 {'Mon','Tue','Wed','Thu','Fri','Sat','Sun','NextMon','NextTue'} 和{'Mon','Tue','Wed','Thu','Fri','Sat','Sun'}。 接下来,从数据集work.data_production_stock中读取了生产数据和 库存数据,从数据集work.data_allocation中读取了分配数据和紧急程度 数据。代码如下: set<str,str> PACKAGE_COLOR; num stock{PACKAGE_COLOR} init 0; num production{PACKAGE_COLOR, DOW} init 0; read data work.data_production_stock nomiss into PACKAGE_COLOR=[package color] stock {d in DOW}<production[package,color,d]=col(d)>; set<str,str,str> WH_PACKAGE_COLOR; num allocation{WH_PACKAGE_COLOR}; num bklg{WH_PACKAGE_COLOR}; read data work.Data_allocation into WH_PACKAGE_COLOR=[warehouse package color] allocation bklg; 下面的代码从数据集work.data_train_schedule、 work.data_ship_direct_route和work.data_ship_shared_route中读取了船舶 和火车的排程及运能数据,参数数组train_cap、ship_direct_cap和 ship_shared_cap保存了从周一到下周二之间火车和船舶按天计算的运 能。参数数组ship_shared_route的索引集为SHIP_ROUTE,索引集 SHIP_ROUTE中的项为 {'WH1','WH2','WH3','WH4','WH5','WH7','WH8'},参数数组 ship_shared_route的取值为数据集work.data_ship_direct_route中变量 shared_route的值。 set<str> TRAIN_ROUTE; num train_cap{TRAIN_ROUTE,DOW_EXT} init 0; read data work.Data_train_schedule nomiss into TRAIN_ROUTE=[warehouse] {d in DOW_EXT}<train_cap[warehouse,d]=col(d)>; set<str> SHIP_ROUTE; num ship_direct_cap{SHIP_ROUTE, DOW_EXT} init 0; str ship_shared_route{SHIP_ROUTE}; read data work.Data_ship_direct_route nomiss into SHIP_ROUTE=[warehouse] ship_shared_route=shared_route {d in DOW_EXT} <ship_direct_cap[warehouse,d]=col(d)>; put SHIP_ROUTE; print ship_shared_route; set<str> SHIP_SHAREDROUTE; num ship_shared_cap{SHIP_SHAREDROUTE,DOW_EXT} init 0; read data work.Data_ship_shared_route nomiss into SHIP_SHAREDROUTE=[shared_route] {d in DOW_EXT}<ship_shared_cap[shared_route,d]=col(d)>; 参数数组ship_shared_route的数据如图24.8所示。 ship_shared_route[WH2]=WH2WH3说明地区仓库WH2存在一条船舶合 并运输线路,线路的名称为WH2WH3; ship_shared_route[WH1]=DIRECT说明地区仓库WH1不存在船舶合并运 输线路。同理,WH3、WH4和WH5都存在各自的合并运输线路,其余 地区仓库都不存在合并运输线路。 图24.8 参数数组ship_shared_route 下面的代码读入了周初码头和火车站的初始库存,这部分库存属于 已经安排调度的存留在码头和火车站的量(按调度计划,这部分量应该 是根据本周一和周二的运输容量安排给在此期间出发的船舶或者火 车)。因此,这部分量势必会占用本周的运能,按照“先到先运”的原 则,适用于本周船舶或火车调度计划的运能应重新计算,新的运能应等 于本周船舶或者火车容量减去码头或火车站的初始库存。 这里运用FOR循环语句,将表示火车运能的参数数组train_cap及表 示船舶运能的参数数组ship_direct_cap和ship_shared_cap进行了更新,并 输出到work.check_remain_train_cap、work.check_remain_ship_direct_cap 和work.check_remain_ship_shared_cap中。这里的CREATE DATA语句是 可选的,主要用于数据检查。 num train_stock_at_port{TRAIN_ROUTE} init 0; num ship_stock_at_port{SHIP_ROUTE} init 0; read data work.Data_stock_at_port(where=(mode='TRAIN')) nomiss into [warehouse] train_stock_at_port=stock_at_port; read data work.Data_stock_at_port(where=(mode='SHIP')) nomiss into [warehouse] ship_stock_at_port=stock_at_port; print ship_shared_route; *** adjust train/ship capacities to transport stock on port ***; for {d in DOW_EXT} do; for {<w> in TRAIN_ROUTE: train_stock_at_port[w]>0} do; if train_stock_at_port[w]>train_cap[w,d] then do; train_stock_at_port[w]=train_stock_at_port[w]-train_cap[w,d]; train_cap[w,d]=0; end; else do; train_cap[w,d]=train_cap[w,d]-train_stock_at_port[w]; train_stock_at_port[w]=0; end; end; for {<w> in SHIP_ROUTE: ship_stock_at_port[w]>0} do; if ship_stock_at_port[w] > ship_direct_cap[w,d] then do; ship_stock_at_port[w] = ship_stock_at_port[w] - ship_ direct_cap[w,d]; ship_direct_cap[w,d] = 0; end; else do; ship_direct_cap[w,d] = ship_direct_cap[w,d] - ship_ stock_at_port[w]; ship_stock_at_port[w] = 0; end; end; for {<w> in SHIP_ROUTE: ship_shared_route[w] in SHIP_SHAREDROUTE and ship_stock_at_port[w]>0} do; if ship_stock_at_port[w] > ship_shared_cap[ship_shared_ route[w],d] then do; ship_stock_at_port[w] = ship_stock_at_port[w] - ship_ shared_cap[ship_shared_route[w],d]; ship_shared_cap[ship_shared_route[w],d] = 0; end; else do; ship_shared_cap[ship_shared_route[w],d] = ship_shared_cap[ship_shared_ route[w],d] - ship_stock_at_port[w]; ship_stock_at_port[w] = 0; end; end; end; create data work.check_remain_train_cap from [warehouse]=TRAIN_ROUTE {d in DOW_EXT}<col(d)=train_cap[warehouse,d]>; create data work.check_remain_ship_direct_cap from [warehouse]=SHIP_ROUTE {d in DOW_EXT}<col(d)=ship_direct_cap[warehouse,d]>; create data work.check_remain_ship_share_cap from [shared_route]=SHIP_SHAREDROUTE {d in DOW_EXT}<col(d)=ship_shared_cap[shared_route,d]>; 保存火车和船舶剩余运能的数据集内容如图24.9所示。 图24.9 火车和船舶剩余容量 以发往地区仓库WH6的数据为例,从数据集work.data_stock_at_port 中可知,周初在火车站,预计本周发往WH6的库存为18,在本周发往 WH6的火车排程中,周一的运能为290。在更新之后,周一的容量应为 272(290-18),如图24.9中所示。 下面的代码声明了模型中的决策变量和隐式变量,汽车的数量不能 是小数,故在声明的时候都加上了关键字INTEGER。由于变量Train、 Ship、Delivery、Alloc、Highway可以用决策变量Train_cart、 Ship_shared、Ship_direct、Alloc_ship、Alloc_train、Alloc_highway等表 示,故根据约束条件将其定义为隐式变量。 set WH = setof{<w,p,c> in WH_PACKAGE_COLOR} w; var Train_cart{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} >=0 integer; impvar Train{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = 10*Train_cart[w,p,c,d]; var Ship_shared{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} >=0 integer; var Ship_direct{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} >=0 integer; impvar Ship{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = Ship_shared[w,p,c,d]+ Ship_direct[w,p,c,d]; impvar Delivery{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = Ship[w,p,c,d] + Train[w,p,c,d]; var Alloc_train{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} >=0 integer; var Alloc_ship{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} >=0 integer; var Alloc_highway{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} >=0 integer; impvar Alloc{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} = Alloc_ship[w,p,c,d] + Alloc_train[w,p,c,d] + Alloc_highway[w,p,c,d]; impvar Highway{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = if d in DOW then Alloc_highway[w,p,c,d] else 0; var Slack_train{w in TRAIN_ROUTE, d in DOW_EXT} >=0; var Slack_ship_direct{w in SHIP_ROUTE, d in DOW_EXT} >=0; var Slack_ship_shared{s in SHIP_SHAREDROUTE, d in DOW_EXT} >=0; 以下代码按照24.2.2节中约束条件的顺序依次声明了各约束条件。 计划的总发运量必须大于等于通过船舶和火车运输的总量,但同时 也必须和调度的总量相等。相应的代码如下: *** balance constraints (note delivery balance is greater or equal because highway can be assigned freely ***; con delivery_balance{<w,p,c> in WH_PACKAGE_COLOR}: allocation[w,p,c] >= sum{d in DOW_EXT} Delivery[w,p,c,d]; con alloc_balance{<w,p,c> in WH_PACKAGE_COLOR}: allocation[w,p,c] = sum{d in DOW} Alloc[w,p,c,d]; 火车运能等于火车的实际运输量加上剩余运能,非合并运输线路的 运能等于非合并运输线路的实际运输量加上剩余运能,合并运输线路的 运能等于合并运输线路的实际运输量加上剩余运能。故有如下约束条 件,这里使用了(:)对索引集进行了筛选。 *** delivery capacities over ship direct/ship share/train ***; con train_delivery_cap{w in TRAIN_ROUTE, d in DOW_EXT: train_cap[w,d]>0}: train_cap[w,d] = Slack_train[w,d] + sum{<(w),p,c> in WH_PACKAGE_COLOR} Train[w,p,c,d]; con ship_direct_delivery_cap{w in SHIP_ROUTE, d in DOW_EXT: ship_direct_cap[w,d]>0}: ship_direct_cap[w,d] = Slack_ship_direct[w,d] + sum{<(w),p,c> in WH_PACKAGE_COLOR} Ship_direct[w,p,c,d]; con ship_share_route_cap{s in SHIP_SHAREDROUTE, d in DOW_EXT: ship_shared_cap[s,d]>0}:ship_shared_cap[s,d] = Slack_ship_shared[s,d] + sum{<w,p,c> in WH_PACKAGE_COLOR: w in SHIP_ROUTE and ship_shared_route[w]=s} Ship_shared[w,p,c,d]; 通过船舶和火车发运的累计总量小于等于生产总量与期初库存的累 计之和,累计的调运总量也必须小于等于生产总量与期初库存累计之 和。 *** delivery and allocation cannot exceed production + stock ***; con delivery_ub{<p,c> in PACKAGE_COLOR,d in DOW_EXT}: sum{<w,(p),(c)> in WH_PACKAGE_COLOR, dd in DOW: index[dd]<=index[d]} Delivery[w,p,c,dd]<= stock[p,c] + sum{dd in DOW: index[dd]<=index[d]} production[p,c,dd]; con allocation_ub{<p,c> in PACKAGE_COLOR,d in DOW}: sum{<w,(p),(c)> in WH_PACKAGE_COLOR, dd in DOW: index[dd]<=index[d]} Alloc[w,p,c,dd]<= stock[p,c] + sum{dd in DOW: index[dd]<=index[d]} production[p,c,dd]; 为了使生产基地仓库实现零库存目标,所以每天的调度量要大于等 于生产下线的数量(考虑生产基地有期初库存的情况)。代码如下: *** all production have to be allocated daily ***; con alloc_lb{<p,c> in PACKAGE_COLOR,d in DOW}: sum{<w,(p),(c)> in WH_PACKAGE_COLOR} Alloc[w,p,c,d] >= production[p,c,d]; 在船舶和火车运输中,当index[d]<7时,累计的调度总量必须要大 于等于实际运输的累计总量;但是,一周中的调度总量必须等于从本周 一到下周二的实际运输总量(此时,不考虑下周一和下周二的调度 量)。代码如下: *** allocation>= delivery over ship/train (note allocation highway = delivery highway) ***; con alloc_accum_train_lb{<w,p,c> in WH_PACKAGE_COLOR,d in DOW: index[d]<7}: sum{dd in DOW: index[dd]<=index[d]} Alloc_train[w,p,c,dd]>= sum{dd in DOW: index[dd]<=index[d]} Train[w,p,c,dd]; con alloc_accum_ship_lb{<w,p,c> in WH_PACKAGE_COLOR,d in DOW: index[d]<7}: sum{dd in DOW: index[dd]<=index[d]} Alloc_ship[w,p,c,dd]>= sum{dd in DOW: index[dd]<=index[d]} Ship[w,p,c,dd]; *** in total allocation = delivery over ship/train ***; con alloc_accum_train_eq{<w,p,c> in WH_PACKAGE_COLOR}: sum{d in DOW} Alloc_train[w,p,c,d] = sum{d in DOW_EXT} Train[w,p,c,d]; con alloc_accum_ship_eq{<w,p,c> in WH_PACKAGE_COLOR}: sum{d in DOW} Alloc_ship[w,p,c,d] = sum{d in DOW_EXT} Ship[w,p,c,d]; 按要求,在第T天调度到码头和火车站的汽车必须在第T天到T+2天 之间从码头或者火车站出发运往各地区仓库,由24.2.3节中的推导可 知,该约束条件可以转化为以下表达式代码: *** allocation in day horizon {1..T} need to be delivered in day horizon {1.. T+2}, i.e. no stock stays at port for more than 2 days ***; con train_stock{<w,p,c> in WH_PACKAGE_COLOR,d in DOW}: sum{dd in DOW: index[dd]<=index[d]} Alloc_train[w,p,c,dd] <= sum{dd in DOW_EXT: index[dd]<=index[d]+2} Train[w,p,c,dd]; con ship_stock{<w,p,c> in WH_PACKAGE_COLOR,d in DOW}: sum{dd in DOW: index[dd]<=index[d]} Alloc_ship[w,p,c,dd]<= sum{dd in DOW_EXT: index[dd]<=index[d]+2} Ship[w,p,c,dd]; 下面代码声明了目标函数Penalty和Priority。 *** Penalize under capacitiy ***; min Penalty = sum{w in TRAIN_ROUTE, d in DOW_EXT} (10-index[d])*Slack_ train[w,d]+ sum{w in SHIP_ROUTE, d in DOW_EXT} (10index[d])*Slack_ship_direct[w,d] + sum{s in SHIP_SHAREDROUTE, d in DOW_EXT} (10-index[d])* Slack_ship_shared[s,d]; max Priority = sum{<w,p,c> in WH_PACKAGE_COLOR, d in DOW_EXT} bklg[w,p,c]* (10-index[d])*(Ship[w,p,c,d] + Train[w,p,c,d]) - Penalty; 通过分析可以得知最优解中一部分决策变量的值。例如,当某地区 仓库不存在火车线路或者火车线路的运能为0时,那么最优解中这条线 路上的实际运输量一定为0。故在求解前,可以使用FIX语句将这部分决 策变量的值固定,然后调用MILP求解器对目标进行求解。代码如下: for {<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} do; if w not in SHIP_ROUTE then fix Ship_shared[w,p,c,d]=0; for {s in SHIP_SHAREDROUTE: w in SHIP_ROUTE and ship_shared_route[w]= s and ship_shared_cap[s,d]=0} fix Ship_shared[w,p,c,d]=0; if w not in SHIP_ROUTE or ship_direct_cap[w,d]=0 then fix Ship_direct[w,p,c,d]=0; if w in SHIP_ROUTE and ship_shared_route[w]='DIRECT' then fix Ship_shared[w,p,c,d]=0; if w not in TRAIN_ROUTE or train_cap[w,d]=0 then fix Train_cart[w,p,c,d]=0; end; solve obj Priority with milp / maxtime=100; 以下代码中声明了多个参数数组来保存最优解,并将最优解输出到 数据集中。 /*Output solution*/ num sol_train{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = round(Train[w,p,c,d].sol); num sol_ship{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = round(Ship[w,p,c,d].sol); num sol_highway{<w,p,c> in WH_PACKAGE_COLOR,d in DOW_EXT} = round(Highway [w,p,c,d].sol); num sol_alloc_train{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} = round(Alloc_train [w,p,c,d].sol); num sol_alloc_ship{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} = round(Alloc_ship [w,p,c,d].sol); num sol_alloc_highway{<w,p,c> in WH_PACKAGE_COLOR,d in DOW} = round(Alloc_highway [w,p,c,d].sol); create data work.sol_ship from [warehouse package color]=WH_PACKAGE_COLOR {d in DOW_EXT}<col(d)=sol_ship[warehouse,package,color,d]>; create data work.sol_train from [warehouse package color]=WH_PACKAGE_COLOR {d in DOW_EXT}<col(d)=sol_train[warehouse,package,color,d]>; create data work.sol_highway from [warehouse package color]=WH_PACKAGE_COLOR {d in DOW_EXT}<col(d)=sol_highway[warehouse,package,color,d]>; create data work.sol_alloc_ship from [warehouse package color]= WH_PACKAGE_COLOR {d in DOW}<col(d)=sol_alloc_ship[warehouse,package,color,d]>; create data work.sol_alloc_train from [warehouse package color]= WH_PACKAGE_COLOR {d in DOW}<col(d)=sol_alloc_train[warehouse,package,color,d]>; create data work.sol_alloc_highway from [warehouse package color]= WH_PACKAGE_COLOR {d in DOW} <col(d)=sol_alloc_highway[warehouse,package,color,d]>; 包含最优解的数据集可以分为两类,一类是名称以sol_开头的数据 集,其中的数据表示每天实际运往某地区仓库的、某种配置、某种颜色 的汽车数量,船运、火车和公路运输分别保存在不同的数据集中;一类 是名称以sol_alloc_开头的数据集,其中的数据表示每天调度运往某地区 仓库的、某种配置、某种颜色的汽车数量。 以数据集work.sol_alloc_ship为例,数据内容如图24.10所示。方框 中的数据可以解读为,周一应调度210辆配置为EXCELLE、颜色为 BLACK的汽车到运往地区仓库WH8的码头,等待后续运输;在本周的 其余时间不需要调度该种配置和颜色的汽车到运往WH8的码头。 图24.10 数据集Work.Sol_alloc_ship部分内容 接下来,将最优解中按地区仓库、按天、按配置、按颜色调度的数 量和实际运输量进行汇总,并输出到的数据集。代码如下: *** solution analysis ***; num ship_daily{w in WH, d in DOW_EXT} = sum{<(w),p,c> in WH_PACKAGE_COLOR} sol_ship[w,p,c,d]; num train_daily{w in WH, d in DOW_EXT} = sum{<(w),p,c> in WH_PACKAGE_COLOR} sol_train[w,p,c,d]; num highway_daily{w in WH, d in DOW_EXT} = sum{<(w),p,c> in WH_PACKAGE_COLOR} sol_highway[w,p,c,d]; num total_delivery_daily{w in WH, d in DOW_EXT} = ship_daily[w,d] + train_daily [w,d] + highway_daily[w,d]; num alloc_ship_daily{w in WH, d in DOW} = sum{<(w),p,c> in WH_PACKAGE_COLOR} sol_alloc_ship[w,p,c,d]; num alloc_train_daily{w in WH, d in DOW} = sum{<(w),p,c> in WH_PACKAGE_COLOR} sol_alloc_train[w,p,c,d]; num alloc_highway_daily{w in WH, d in DOW} = sum{<(w),p,c> in WH_PACKAGE_COLOR} sol_alloc_highway[w,p,c,d]; num total_alloc_daily{w in WH, d in DOW} = alloc_ship_daily[w,d] + alloc_train_daily[w,d] + alloc_highway_daily[w,d]; create data work.sol_ship_daily from [warehouse]=WH {d in DOW_EXT}<col(d)= ship_daily[warehouse,d]>; create data work.sol_train_daily from [warehouse]=WH {d in DOW_EXT}<col(d)= train_daily[warehouse,d]>; create data work.sol_highway_daily from [warehouse]=WH {d in DOW_EXT} <col(d)= highway_daily[warehouse,d]>; create data work.sol_total_delivery_daily from [warehouse]=WH {d in DOW_EXT}< col(d)=total_delivery_daily[warehouse,d]>; create data work.sol_alloc_ship_daily from [warehouse]=WH {d in DOW}<col(d)= alloc_ship_daily[warehouse,d]>; create data work.sol_alloc_train_daily from [warehouse]=WH {d in DOW}<col(d)= alloc_train_daily[warehouse,d]>; create data work.sol_alloc_highway_daily from [warehouse]=WH {d in DOW}<col(d)= alloc_highway_daily[warehouse,d]>; create data work.sol_total_alloc_daily from [warehouse]=WH {d in DOW}<col(d)= total_alloc_daily[warehouse,d]>; 以数据集work.sol_alloc_train_daily和work.sol_train_daily为例,数据 内容如图24.11所示。 图24.11 部分最优解 以图24.11中的WH4为例,在work.sol_alloc_train_daily中周一的调度 量为130,在work.sol_train_daily中周一的实际运输量为130;周二、周 三和周四的调度量分别为60、65和25,周二和周三的实际运输量为0, 周四的实际运输量为150,可见正好将周二、周三和周四的调度量集中 在周四发出;周五、周六和周日的调度量分别为0、150和0,周五到周 日的实际运输量都为0,周六调度的150辆在下周一从火车站出发。 在代码的最后,分析了最优解中船舶和火车的利用率,并输出到数 据集work.check_ship和work.check_train中。其中,UNION集合运算符将 船舶的合并运输线路和非合并运输线路都赋予集合ROUTES。 set<str> ROUTES init {}; num capcity{ROUTES, DOW_EXT} init 0; num shipment{ROUTES, DOW_EXT} init 0; for {r in SHIP_SHAREDROUTE, d in DOW_EXT} do; ROUTES = ROUTES union {r}; capcity[r,d]=ship_shared_cap[r,d]; for {w in SHIP_ROUTE: ship_shared_route[w]=r} do; shipment[r,d]=shipment[r,d]+ sum{<(w),p,c> in WH_PACKAGE_COLOR} Ship_shared[w,p,c,d].sol; end; end; for {w in SHIP_ROUTE, d in DOW_EXT} do; ROUTES = ROUTES union {w}; capcity[w,d]=ship_direct_cap[w,d]; shipment[w,d]= sum{<(w),p,c> in WH_PACKAGE_COLOR} Ship_direct [w,p,c,d].sol; end; create data work.check_ship from [route]=ROUTES {d in DOW}<col(d)=(shipment [route,d]||'/'||capcity[route,d])>; create data work.check_train from [warehouse]=TRAIN_ROUTE {d in DOW}<col(d)= (train_daily[warehouse,d]||'/'||train_cap[warehouse,d])>; quit; 数据集work.check_ship和work.check_train的内容如图24.12所 示。“/”前面的数字表示实际运输量,“/”后面的数字表示运能。 图24.12 24.2.6 最优解中船舶和火车运能利用率分析 功能与技巧汇总 在这个案例的建模和求解过程中,除了和第一个案例一样展示了 READ DATA、IMPVAR和CREATE DATA等语句的使用方法和技巧以 外,还展示了PROC OPTMODEL的一些其他功能: ·使用.SOL后缀获取最优解中决策变量的取值。 ·使用COL(表达式)计算数据集中变量的名称。 ·使用FOR语句处理变量和参数数组。 ·使用FIX语句固定决策变量的取值。 ·使用UNION集合运算符生成集合。 ·使用SETOF集合运算符生成集合。 24.3 本章小结 本章结合了两个非常贴近实际项目的案例,介绍了运用SAS/OR解 决实际优问题的基本分析过程。第一个实例中侧重介绍了在进行多目标 优化时,如何将前次的优化结果作为二次优化的初始解,从而提高二次 优化的求解效率;第二个实例着重讨论了如何将实际的问题转化成数学 问题,并说明了进行数据验证的重要性,同时展示了PROC OPTMODEL中集合和变量的操作技巧。 第四篇 SAS智能平台架构体系 第25章 SAS智能平台及行业解决方案 第26章 SAS应用的架构规划 第27章 SAS智能平台安全管理 第28章 SAS智能平台的高可用性 第25章 SAS智能平台及行业解决方案 前面以24章的篇幅介绍了SAS在数据处理、统计分析以及预测和优 化等方面的技术知识。对这些技术知识的深入掌握和灵活应用,足以使 大家成为一个优秀的SAS技术专家。在拥有这些专业的SAS知识之后, 如何在实际的商业环境中,帮助大型商业客户解决他们的业务问题,建 立强大、安全并且可靠的应用系统,则是本书后续章节的目标。 为了更好并且更快捷地帮助用户发挥SAS在数据管理、数据分析和 预测优化等领域的核心功能,SAS提供了SAS智能平台(SAS Platform for Business Analytics)。SAS智能分析平台是一个完整的从端到端的基 础架构平台。基于这个平台,可以实现(但不局限于)下述任务: ·各种数据源的获取 ·数据的处理和管理 ·数据分析 ·信息的创建、管理和分发 ·元数据管理 在SAS智能平台之上,基于在多个行业领域长期帮助客户解决特定 业务问题的经验,针对不同行业中的特定业务问题提供了相应的解决方 案,例如,在客户智能(Customer Intelligence)、风险管理(Risk Management)和财务管理(Financial Management)等领域都有一系列 的解决方案。这些解决方案集成了为解决某个领域内具有代表性的业务 问题所需要的数据模型、对数据的转换和处理,以及建立在完成转换和 处理后的数据之上的分析和优化模型等。 本章主要介绍SAS智能平台,以及面向各行业的解决方案和SAS的 高性能分析产品等。但是,本章并不会讲解这些产品或解决方案的详细 功能,有兴趣的读者可参考具体的产品或解决方案的文档进行学习。 25.1 SAS智能平台 SAS智能平台将SAS的数据处理、分析、预报和优化等众多功能和 相关产品无缝集成起来,减少了管理和部署成本,并且提供了多种技术 来满足不同用户的需求。这样用户就可以基于一个统一的平台,对企业 的各种数据源(包括各类ERP系统的数据)进行整合了,并且可创建数 据仓库,执行数据分析和数据挖掘,还可通过Web浏览器查询信息并产 生报表。 SAS智能平台使用n层结构的设计理念,使其具有跨计算机资源分 布各层的功能。该体系架构支持快速扩展以符合工作任务的需要。对于 大型的企业和组织机构,各层可以在不同操作系统的多台计算机上安 装。对于原型设计、演示或小型企业,则可以将各层安装在同一台计算 机上。 该体系结构包含以下4层(如图25.1所示)。 图25.1 SAS智能平台体系架构 (1)数据源 数据源用于存储企业数据。所有现有的数据资产,无论是存储在第 三方的数据库管理系统或SAS表中的,还是存放在ERP系统表中的,都 可以被SAS智能平台整合到SAS系统中使用。 (2)SAS服务器 SAS服务器执行对数据的处理和管理。有多种SAS服务器可供选 择,以便处理不同负载类型的任务。软件将来自客户端的请求合理地分 布到各个服务器,以便能够满足多个客户端的请求。 (3)中间层 中间层使用户能够通过Web浏览器访问数据和信息,并调用SAS功 能。该层是基于Web的结构,用于创建报表和发布信息,并且可将分析 和处理请求传递给SAS服务器进行处理。 (4)客户端 客户端为用户提供对智能信息的访问和管理。对于大多数信息消费 者而言,可以仅通过Web浏览器来完成报表和分析任务。对于高级分析 设计任务,必要时可以使用安装在用户桌面环境中的SAS客户端软件。 同时,SAS还提供了对移动设备的支持。 这4层是按照执行计算任务和要求资源的软件类别进行划分的,并 不表示各层必须为单独的计算机或计算机组。 25.1.1 数据层 SAS智能平台的数据层可使用如下数据源: ·SAS数据集,也称为SAS表,是由SAS创建和处理的SAS文件,类 似于关系型数据库中的表。 ·SAS Scalable Performance Data(SPD)数据引擎表,支持对分区的 数据集进行多线程访问。 ·SAS SPD服务器,供用户对SAS SPD服务器上的数据进行密集型的 处理和访问。 ·SAS OLAP立方体,是提前汇总过的多维数据,可以让用户从多个 角度对数据进行分析,本章后面的小节会进一步介绍。 ·SAS Web Infrastructure Platform Data Server,中间层存储数据的默 认位置。中间层数据包含报警(Alert)、注释(Comment)和工作流 (Workflow),以及SAS Content Server中的数据。这些数据也可以存 储在第三方数据库管理系统中。 ·第三方数据存储和ERP系统中的数据。SAS/ACCESS接口提供了对 各种数据存储的直接访问。 25.1.2 SAS服务器 SAS服务器包括SAS元数据服务器,以及执行SAS分析和报表处理 的计算服务器。SAS计算服务器包括SAS OLAP服务器(OLAP Server)、SAS工作区服务器(Workspace Server)、SAS共享池工作区 服务器(Pooled Workspace Server)、SAS存储过程服务器(Stored Process Server)。与SAS工作区服务器、SAS共享池工作区服务器和 SAS存储过程服务器密切相关的一个组件是SAS Object Spawner,用于 处理对所在计算机上的这3类服务器的请求,后面会有进一步的介绍。 这些服务器通常可以通过桌面客户端或在中间层运行的Web应用进 行访问。有时也可将SAS元数据服务器与计算服务器分开安装部署,它 们分别称为元数据层(Metadata Tier)和计算层(Computer Tier)。 注意 在SAS智能平台中,“服务器”并非指通常的硬件服务器, 而是指一个或多个用来满足来自客户端对数据或服务请求的软件进程。 1.SAS元数据服务器 SAS元数据服务器提供对元数据中心存储库的访问,并对元数据进 行统一集中管理,这样可以保证所有用户访问的信息一致。元数据存储 库由该部署中的所有SAS应用共享,该存储库存储和管理下列信息: ·SAS智能应用需要访问的企业数据源和数据结构。 ·SAS应用创建和使用的内容,包括信息映射(Information Map)、 OLAP立方体、报表定义、存储过程定义,以及Portal内容定义等。 ·SAS服务器以及为访问第三方数据所创建的服务器。 ·允许使用该SAS系统的用户和用户组信息。用户可通过元数据服务 器或主机环境、Web域、第三方数据库等外部系统进行认证。 ·用户和组对资源进行访问的授权。SAS使用基于元数据的授权层补 充保护SAS系统。 2.SAS OLAP服务器 SAS OLAP服务器是多维数据服务器,常用于向商业智能应用提供 预先汇总的数据立方体(Cube)。该服务器被设计用来在查询时快速提 供汇总视图,而不必考虑产生这些汇总的数据量,从而减少了传统后端 存储系统的负载。提交到SAS OLAP服务器的数据查询将使用多维表达 式(Multimensional Expression,MDX)语言。 3.SAS工作区服务器 SAS工作区服务器使得SAS客户端应用程序能够将SAS代码提交到 SAS会话中执行。例如,当使用SAS Data Integration Studio提交ETL作业 进行数据处理时,其所生成和管理的SAS代码将提交到工作区服务器执 行。类似地,SAS Enterprise Guide和SAS Enterprise Miner等客户端应用 也可以将分析任务提交到工作区服务器上运行。如果有需要,还可运行 多个工作区服务器实例来支持并发的请求。 4.SAS共享池工作区服务器 SAS共享池工作区服务器是使用服务器端轮询(Server-side Polling)的工作区服务器。这种配置为客户端维持可以重用的一定数量 的工作区服务器进程,这样每次客户端在连接该服务器时,就不必重新 创建新的工作区服务器进程了,从而避免了相关的资源消耗。SAS Information Map Studio、SAS Web Report Studio,以及SAS Information Delivery Portal等客户端都使用共享池工作区服务器查询后端数据库中的 数据。 5.SAS存储过程服务器 在一个多客户端环境中,SAS存储过程服务器将执行并交付SAS存 储过程运行的结果。SAS存储过程是指集中注册并存储在一个指定位置 的、能够被用户或客户端应用程序执行的SAS程序。需要时可运行多个 存储过程服务器实例,以支持负载。存储过程常用于运行频繁且运行时 间较短的任务,它还可交互式地提供运行时使用的参数。 6.SAS Object Spawner SAS Object Spawner是运行在工作区服务器、共享池工作区服务 器,以及存储过程服务器所在主机上的进程。它监听对这些服务器的请 求,认证提出请求的客户端,并且在需要时启动服务器进程响应请求。 在一个共享池工作区服务器的配置中,SAS Object Spawner用于维持客 户端可重用的工作区服务器进程集合。当为SAS计算服务器配置了服务 器负载均衡(Load Balancing)时,SAS Object Spawner还会在同类型的 计算服务器进程间平衡负载。 25.1.3 中间层 SAS智能平台的中间层为SAS的Web应用提供了可执行环境。这些 产品在Web应用服务器中运行,并将请求的信息发送到用户的Web浏览 器上,或从用户的Web浏览器上接收信息。这些中间层的Web应用依赖 于SAS服务器层的服务器来达成数据处理和分析等任务。 中间层包含了如下SAS软件组件: ·SAS Web应用服务器和SAS Web服务器。 ·SAS Web应用,包括SAS Web Report Studio、SAS Information Delivery Portal、SAS BI Portlets、SAS BI Dashboard、SAS Environment Manager等其他基于Web应用的SAS产品和解决方案。 ·SAS Web Infrastructure Platform,包括SAS Content Server和其他 SAS智能平台体系架构中的应用和服务。 25.1.4 客户端 SAS智能平台的客户端为使用和管理智能分析平台提供了界面和工 具。在SAS智能平台实现的所有功能,包括对平台本身进行管理和维 护,对数据进行处理、分析和管理,报表的制作、查询和浏览等,都可 以通过各种客户端实现。SAS的客户端分为Web应用客户端和桌面客户 端。 运行在Windows桌面的SAS客户端主要包括SAS Add-In for Microsoft Office、SAS Data Integration Studio、SAS Enterprise Guide、 SAS Enterprise Miner、SAS Forecast Studio、SAS Information Map Studio、SAS Management Console、SAS Model Manager、SAS OLAP Cube Studio、SAS Workflow Studio等。 常用的Web应用客户端有SAS Information Delivery Portal、SAS BI Dashboard、SAS Environment Manager、SAS Web Report Studio等。 此外,还可以使用移动设备(iPad和Android)查看SAS报表。 25.2 SAS商业智能 SAS在商业智能(Business Intelligence)领域向用户提供及时、相 关和可操作的信息,帮助用户更快更明智地做出决策。根据不同用户的 业务需求,SAS提供的商业智能产品主要如下: ·SAS Visual Analytics ·SAS Office Analytics ·SAS Enterprise BI Server 下面将介绍SAS Office Analytics以及SAS Enterprise BI Server的主要 客户端软件。SAS Visual Analytics使用了高性能分析技术,会在后面的 章节专门介绍。 25.2.1 SAS Office Analytics SAS Office Analytics为服务器/客户端结构,通过应用Microsoft Office(利用Microsoft Office集成的菜单和工具栏)来对后端的SAS服务 器进行访问,进而利用SAS提供的分析功能对数据进行分析、或进行报 表制作。当然,也可用Microsoft SharePoint门户,以及Windows的桌面 应用SAS Enterprise Guide来实现对后端SAS服务器的访问。 1.SAS Add-In for Microsoft Office SAS Add-In for Microsoft Office是SAS Office Analytics的客户端,其 扩展了Microsoft Office应用程序的功能,使用户可以使用Microsoft Excel、Microsoft Word、Microsoft PowerPoint和Microsoft Outlook的集成 菜单和图标对SAS智能平台的数据进行访问,并利用后端的SAS服务器 资源对数据进行分析,同时生成报表。这极大地方便了熟悉Microsoft Office但又需要使用SAS分析处理功能的广大用户。 通常,SAS Add-In for Microsoft Office为Excel、Word和PowerPoint 提供了相同的功能,包括查看报表和仪表板、运行分析、生成报表以及 与其他用户共享相应的SAS内容等。不过,在Excel中还可使用更多的功 能,例如打开、复制、编辑数据源等。值得一提的是,SAS Add-In for Microsoft Office为Outlook提供的功能却相对独特,在Outlook中可以查 看报表和仪表板,并与其他用户共享信息,但是不能创建报表或运行 SAS任务。 在安装SAS Add-In for Microsoft Office时,SAS选项卡会自动集成 到Excel、Word和PowerPoint中。使用该SAS选项卡中提供的菜单或工 具,可以直接在这些Microsoft Office应用程序中使用SAS分析和报表功 能。SAS Add-In for Microsoft Office包括了近80个SAS任务,能够执行 各种分析,例如线性回归、非线性回归、多元分析、时间序列分析、单 向频率和汇总统计等。还可以刷新这些分析,以保证分析使用最近的数 据,从而使分析结果中包含最新信息。分析的结果也可以很容易地与其 他用户共享。 图25.2给出了使用SAS Add-In for Microsoft Office运行线性模型的结 果。 图25.2 SAS Add-In for Microsoft Office中的线性分析结果 在SAS Add-In for Microsoft Office中还可以打开或运行在SAS Enterprise Guide、SAS Web Report Studio或SAS Visual Analytics等其他 SAS应用里创建的报表或存储过程。 2.SAS Enterprise Guide SAS Office Analytics软件包中包含了Windows桌面应用SAS Enterprise Guide(SAS EG)。它通过图形化界面提供了对SAS分析能力 的访问。用户可通过其中集成的向导完成分析过程,并且能够很容易地 创建报表、图形和图表,以及交付分析结果和信息。 SAS EG提供了各种数据的导入、处理、描述、图形、分析等预定 义任务。在EG中可创建流,还可以在流中使用这些预定义任务对数据 进行处理和分析,最后生成ODS输出或SAS报表等,如图25.3所示。所 生成的结果可通过SAS发布框架进行分发。 在SAS EG中可开发SAS程序,其集成了带语法帮助的SAS程序编辑 器,能够提高SAS程序的开发效率。在EG中,所开发的SAS程序可以创 建为存储过程。SAS信息映射、SAS Web Report Stuido等可以调用这些 存储过程来执行SAS程序,并产生输出和结果。 用户还可以在SAS EG中查看使用SAS Web Report Studio创建的报 表,并且可对OLAP Cube进行钻取、动态切片和探索等研究。 图25.3 SAS Enterprise Guide中的流 SAS EG集成了SAS网格计算能力和高性能分析能力。在SAS用以实 现高性能大数据支持的网格计算环境或内存分析环境中,通过适当的配 置,用户可以在SAS EG中将任务提交到该环境中运行,以提高任务运 行效率。 3.SAS Management Console SAS Management Console是一个Java应用程序,它提供了对整个智 能价值链资源进行管理的统一界面。此统一界面可用于执行创建和维护 跨多个平台的集成环境所要达成的管理任务,而且不必为计算环境中的 每个应用使用单独的管理界面。在SAS Management Console中,可以管 理以下内容: ·服务器定义 ·库定义 ·用户、组和角色定义 ·资源访问控制 ·元数据存储库 ·作业调度 通过SAS Management Console,不但可以创建和维护每种计算资源 的元数据,而且可以创建和维护元数据来定义访问和管理这些计算资源 的权限控制。这些元数据存储在SAS元数据服务器上的存储库中,可以 被应用程序使用。 图25.4为SAS Management Console的界面。 图25.4 25.2.2 SAS Management Console界面 SAS Enterprise BI Server SAS Enterprise BI Server(企业商业智能服务器)为商业智能报告 提供全面的解决方案,包括Web形式的门户功能、可定制的仪表板以及 报告调度等。本节主要介绍SAS Enterprise BI Server的各个主要客户 端。上节介绍过的SAS Enterprise Guide和SAS Management Console,也 属于它的组成部分,因此这里不再介绍。 1.SAS Information Map Studio SAS信息映射(Information Map)是在数据仓库的数据源的业务元 数据层上实现的,它只会描述数据的结构和内容,并不包含任何物理数 据。作用是给业务用户提供一种从业务角度去理解物理数据的工具,从 而使业务人员可以不依赖数据仓库的管理员而自主地使用和分析数据, 并获取结果。 SAS信息映射是通过SAS Information Map Studio创建的,其数据源 可以是在SAS中使用的任意数据源。如图25.5所示为在SAS Information Map Studio中设计或查看信息映射的界面。左侧窗口显示元数据中的所 有数据源,中间为所选取的用于创建信息映射的表,右侧为信息映射。 图25.5 SAS信息映射 在信息映射中可以包含数据项和过滤器。数据项可以是数据源中的 数据字段或数据字段的计算项,过滤器包含对数据取子集的条件,这些 都会用于创建查询。还可使用文件夹来组织数据项和过滤器,这样用户 在使用信息映射时就可以很容易地找到需要的信息了。 信息映射使数据存储对用户透明,无论数据是SAS数据集、多维数 据,还是第三方数据源,它们所使用的信息映射方式都是相同的。所生 成的信息映射可以在Base SAS、SAS Add-In for Microsoft Office、SAS Enterprise Guide、SAS Information Delivery Portal、SAS Marketing Automation、SAS Web Report Studio、SAS BI Dashboard等SAS产品中使 用。 2.SAS OLAP Studio SAS在线分析处理(Online Analytical Processing,OLAP)是用于创 建决策支持软件的技术。OLAP使用户能够快速地从多个业务角度(多 维)分层级地分析汇总生成的多维立方体。OLAP技术通过将可预测的 分析维度和分析路径提前汇总到多维立方体中,来提供比传统数据库访 问工具更好的性能。 通常,SAS的多维立方体(OLAP Cube)在客户端应用程序SAS OLAP Cube Studio中设计、创建和维护。创建汇总OLAP Cube的工作由 SAS工作区服务器完成,而使用OLAP Cube时提交的查询则是通过SAS OLAP服务器进行处理的。 如图25.6所示为在SAS OLAP Cube Studio中创建立方体的向导。 图25.6 创建SAS OLAP立方体向导 所创建的多维立方体可以在SAS Enterprise Guide、SAS Information Map Studio、SAS Web Report Studio、SAS Add-In for Microsoft Office, 以及SQL Pass-Through Facility for OLAP等SAS产品中使用。如图25.7所 示为在SAS Web Report Studio中引用多维立方体的示例。 图25.7 引用SAS OLAP立方体 3.SAS Web Report Studio SAS Web Report Studio是用于查看、交互、创建和分发报告的Web 应用。用户可以根据当前的需要简单地打开存在的报表并交互信息,对 报表进行查看、回复或添加评论,也可以通过拖放功能来添加提示,并 设计表、图形和文本的布局,从而创建良好格式的报告。还可以通过预 定周期性地产生报表,并可使用电子邮件进行分发。 如图25.8所示为在SAS Web Report Studio中查看报表的示例。 图25.8 SAS Web Report Studio中创建的报表 在创建报表时可使用到前面介绍的信息映射,用户不需要理解复杂 的数据结构和数据库即可完成报表的创建。此外,在SAS Report Studio 中还可以运行SAS存储过程,将其呈现为报表的一部分。 SAS Web Report Studio将查询、报表和分析能力集成到了一个基于 Web的工具中,这样更大范围的用户能够使用它来满足复杂的信息需 求。 4.SAS BI Dashboard SAS BI Dashboard支持用户使用仪表板来监控反映组织运营状况的 关键绩效指示器。仪表板中可包含图形、文本、颜色和超链接。SAS BI Dashboard提供基于Web的界面来创建、维护和查看仪表板。 SAS BI Dashboard中的仪表板易于设计、定制和创建。在仪表板中 可使用多种数据源,包括SAS信息映射、SAS存储过程、SQL查询以及 SAS表等,如图25.9所示。在仪表板的设计器中,可使用拖放功能对仪 表板的内容进行定制设计,从而提供所见即所得的仪表板视图。 图25.9 BI Dashboard设计器 业务用户可在BI Dashboard查看器中查看所创建的仪表板。在仪表 板查看器中,可以以交互方式查看数据,为喜欢的仪表板和指示器添加 书签,创建个人化警告,以及与其他业务用户分享关于仪表板中指示器 的评论等。如图25.10所示为在SAS BI Dashboard查看器中查看仪表板的 示例。 5.SAS Information Delivery Portal SAS Information Delivery Portal(SAS Portal)提供基于Web的用户 界面,用户能够通过该界面访问企业的各种信息并在其中进行导航。这 些信息包括报表、图表、Web应用、文档,以及内部或外部网页链接 等。还可以在SAS系统中进行安全性配置,使用户只能看到自己被授权 访问的信息。 SAS Portal的个人化特性使用户能够创建和定制Portal页面和内容, 使其只包含用户感兴趣的信息,并以期望的形式展现。在Portal页面 里,可以订阅发布频道以持续获取更新信息。 SAS Portal使用SAS BI Portlet组织Portal页面的信息。如图25.11所示 给出了SAS Portal页面的示例。 图25.10 查看仪表板 图25.11 SAS Portal页面 6.SAS BI Portlets SAS BI Portlets是基于JSR 168规范的Portlets,可以无缝地集成到 SAS Information Delivery Portal中。SAS BI Portlets使用户能够访问、查 看或操作存在于SAS元数据服务器或SAS Content Server中的内容。SAS BI Portlets套件包含SAS Collection Portlet、SAS Navigator Portlet、Report Portlet、Stored Process Portlet、BI Dashboard Portlet以及Diagnostics Portlet。 (1)SAS Collection Portlet 用户使用SAS Collection Portlet能够创建各种可以通过SAS内容查看 器访问的SAS内容项的列表,并提供了联合搜索界面实现添加和删除 项。SAS Information Delivery Portal仅支持一部分SAS内容项,不支持 Portlet、页面和页面模板,也不直接支持发布框架(Publication Framework),但是可以通过使用频道间接显示发布包。 (2)SAS Navigator Portlet 用户可以使用SAS Navigator Portlet导航元数据服务器中的文件夹, 并定位SAS内容,例如报表和存储过程,也可访问SAS Content Server中 的WebDAV文件夹及其内容。 (3)SAS Report Portlet SAS Report Portlet允许用户以静态HTML格式显示SAS报表。使用 该Portlet,用户可在SAS Web Report Studio内进行钻取,以利用所有报 表的功能。 (4)SAS Stored Process Portlet SAS Stored Process Portlet使用户能够显示存储过程的输出。在编辑 模式中,用户可以管理运行存储过程时需要使用的参数。 (5)SAS BI Dashboard Portlet SAS BI Dashboard Portlet使用户能够访问SAS BI Dashboard,并且 可显示在SAS BI Dashboard中创建的仪表板。 (6)SAS Diagnostics Portlet Diagnostics Portlet使管理员能够确定SAS Portal环境的当前状态。其 他用户根据其权限或许能够查看部分信息。 7.SAS Web Parts for Microsoft SharePoint SAS Web Parts for Microsoft SharePoint使用户能够在Microsoft Sharepoint页面中显示SAS BI Dashboard和SAS分析,包括以下内容: ·SAS BI Dashboard Web Part,显示仪表板和KPI,使用户能够监视 企业绩效。 ·SAS Stored Process Web Part,使用户能够在SharePoint页面中查看 存储过程的运行结果。 25.3 SAS数据管理和集成 数据集成指的是整合来自各种数据源的数据,并产生统一的集成数 据的过程。SAS管理和集成软件及技术可以连接、获取、存储和管理数 据,并应用SAS数据质量软件,对数据进行清洗和增强,从而提供一 致、准确、可靠的数据访问。在必要的时候,SAS的数据管理和集成功 能可以将数据写回各种数据存储、数据流、应用程序和系统中,例如, ERP系统、关系型数据库管理系统、平面文件、各种历史遗留系统、消 息队列和XML文件等。 25.3.1 SAS数据集成 SAS数据集成的主要产品为SAS Data Integration Server,其客户端 为SAS Data Integration Studio。通过SAS Data Integration Studio,用户可 以创建作业提取、转换和加载整个企业的所有数据,从而产生一致准确 的信息,还可以在不同的业务系统和数据源之间迁移、同步和复制数 据。 SAS Data Integration Studio提供了图形化界面,设计人员通过鼠标 单击就能够构建流程,快速识别输入和输出,在元数据中创建业务规 则,并且可快速生成数据仓库、数据集市和数据流等。如图25.12所示 为在SAS Data Integration Studio中对两个表进行联合的作业流示例。 图25.12 SAS Data Integration Studio的联合作业流 SAS Data Integration Studio中还集成了SAS数据质量软件。利用SAS Data Integration Studio除了可以通过数据转换来改变、重新格式化并整 合数据以外,还可以应用实时数据质量技术来对数据进行清理,甚至可 以构建可重用的业务规则库。 25.3.2 SAS数据质量管理 SAS Data Quality Server(数据质量服务器)包含下列组成部分: ·质量知识库(Quality Knowledge Base,QKB) ·执行质量知识库中预定义的数据质量操作的SAS语言元素 ·与DataFlux Data Management Platform(DataFlux数据管理平台)进 行交互的组件 SAS Data Quality Server与DataFlux Data Management Platform进行通 信,以提供一个集成的数据管理系统。该集成系统通过数据质量、数据 集成、主数据管理,以及数据访问接口来管理数据资产。 DataFlux Data Management Studio是与SAS Data Quality Server交互 的客户端软件。 25.3.3 DataFlux数据管理平台 DataFlux数据管理平台使用户能够以集中的方式设计、部署和维护 企业数据。该平台的主要组件包括DataFlux Data Management Server、 SAS Federation Server、DataFlux Authentication Server、DataFlux Web Server,以及客户端DataFlux Data Management Studio和DataFlux Web Studio。 1.DataFlux Data Management Server DataFlux Data Management Server(DataFlux数据管理服务器)通过 集成实时数据质量、数据集成和数据管理程序,在企业各个方面应用部 署数据质量、数据集成、业务流程和主数据管理(Master Data Management,MDM),从而提供一致、准确、可靠的数据访问。使用 DataFlux数据管理服务器,还可以跨应用程序和系统复制业务规则,建 立企业统一的数据资源系统。 该数据管理服务器是采用面向服务的架构(Service-Oriented Architecture,SOA)的应用程序服务器,能够执行实时数据服务和实时 流程服务。这些服务可以通过任何的Web服务应用程序来调用,也可以 将现有的批处理作业转换为实时服务,以重用所开发的数据迁移或加载 数据仓库业务逻辑,以及在数据录入点启用实时服务,以确保整个企业 使用一致、准确、可靠的数据。同时,该服务器还提供了应用编程接口 API来调用质量知识库QKB,以进行数据分解、标准化、匹配键的生 成、地址验证等处理。 2.SAS Federation Server SAS Federation Server(SAS联合服务器)是使用多线程的多用户技 术对异构数据源进行整合的可扩展数据服务器。该服务器充当集线器, 通过访问、管理和共享SAS数据以及第三方关系型数据库来为客户端提 供数据。 3.DataFlux Authentication Server DataFlux Authentication Server(DataFlux认证服务器)提供跨多个 域和多个操作环境的认证管理中心功能。 4.DataFlux Data Management Studio DataFlux Data Management Studio可以单独使用,也可与上述服务器 一起使用,是开发、测试和管理DataFlux Data Management Server的客户 端。它提供了元数据分析、数据特征描述、数据质量、数据整合、数据 监视、地址验证、数据丰富以及主数据管理的界面。通过DataFlux Data Management Studio,用户能够创建、测试并上传批处理作业、特征描绘 作业、实时数据服务,以及实时流程服务。 如图25.13所示为DataFlux Data Management Studio的作业窗口示 例。 图25.13 DataFlux Data Management Studio作业窗口 5.DataFlux Web Studio和DataFlux Web Server DataFlux数据管理还包括DataFlux Web Studio。DataFlux Web Studio 提供了基于Web的管理工具,使用户能够通过Web浏览器执行数据管理 任务。所有DataFlux Web Studio模块支持的作业都由DataFlux Web Server运行。 25.3.4 SAS主数据管理 主数据管理用于寻找或创建一条记录,该记录包含要了解一个特定 的人、位置、产品、供应商、业务或其他实体需要知道的所有信息。该 记录称为实体的幸存记录,也称为主记录或黄金记录。主数据管理的目 标是,对于业务重要的每个实体,只有一个主记录定义。实体是用于主 数据管理中业务流程的核心要素。这个实体可以是在业务系统中使用的 单个客户、产品、网站、账户或任何其他数据元素。 SAS MDM是通过SAS Data Management Console(SAS数据管理控 制台)访问的基于Web的应用程序。SAS MDM将来自不同数据源的信 息整合到一个主记录,以便为企业数据提供唯一、准确、统一的视图。 使用SAS可以开发主数据管理流程,分析现有数据资源,构建信息的统 一视图并管理数据主视图。 如图25.14所示为登录SAS Data Management Console后的界面示 例。 图25.14 SAS Data Management Console界面 SAS MDM还包括SAS数据修复(SAS Data Remediation)和SAS任 务管理器(SAS Task Manager)。SAS数据修复使用户能够通过SAS MDM批处理作业和实时流程中的业务规则进行管理,并触发校正数据 中存在的问题,如图25.15所示。 图25.15 SAS数据修复示例 SAS任务管理器是整合SAS工作流技术的补充应用程序。它使用户 能够直接访问可能已经由另一个SAS应用启动的工作流。用户可以启 动、停止和转变已上载至SAS工作流服务器环境的工作流。 如图25.16所示为SAS任务管理器界面示例。 图25.16 SAS任务管理器界面 25.4 SAS商业分析 本节主要介绍SAS提供的数据挖掘和文本挖掘的产品(SAS Enterprise Miner和SAS Text Miner),以及面向各个业务领域的主要商 业分析解决方案。 25.4.1 SAS Enterprise Miner SAS Enterprise Miner简化了数据挖掘过程,这样就能够通过对整个 企业的海量数据进行分析来建立高精度的预测和描述模型了。对于各种 行业的不同业务问题,数据挖掘都适用,例如欺诈检测、客户保留和流 失、数据库营销、市场分群、风险分析、客户满意度和投资组合分析 等。 在SAS Enterprise Miner中,数据挖掘过程包括以下(SEMMA)步 骤: 1)对数据进行采样(Sample); 2)探索(Explore)数据以理解数据; 3)通过创建、选择变量或对变量进行变换以修正(Modify)数 据; 4)使用分析工具和技术对数据进行建模(Model); 5)对该数据挖掘过程的有用性和可信性进行评估(Assess)。 在一次分析中不一定会包括所有的SEMMA步骤,但有时也有可能 需要重复一个或多个步骤多次,以得到满意的结果。 在SAS Enterprise Miner中,数据挖掘过程由通过从工具栏中拖拽节 点到流程图工作区创建流程图来实现。工具栏中的节点使用SEMMA分 类组织,如图25.17所示为在SAS Enterprise Miner中使用流程图进行建模 的示例。 图25.17 SAS Enterprise Miner中的流程图 SAS Enterprise Miner还提供了先进的可视化工具,使用户能够在多 维直方图中检查数据,并且可以图形的方式比较建模结果,如图25.18 所示。 完成了SEMMA步骤后,可以将来自一个或多个冠军模型的完整打 分过程生成为SAS代码、C代码或Java代码。SAS Enterprise Miner所生成 的模型可以注册到SAS元数据服务器中,以便在SAS Enterprise Guide、 SAS Data Integration Studio等SAS应用分享。 图25.18 SAS Enterprise Miner的模型比较结果 SAS Model Manager可与SAS Enterprise Miner完全集成,从而对整 个模型的开发、测试和生产环境进行管理,是对数据挖掘过程的一种补 充。 此外,SAS High-Performance Data Mining和SAS High-Performance Text Mining这类高性能分析产品也集成到了SAS Enterprise Miner中。其 表现为在SAS Enterprise Miner工具栏选项卡HPDM中的一系列高性能分 析节点,在流程图中使用这些节点可使数据挖掘或文本挖掘的过程在 SAS高性能分析框架中运行,以提高执行性能。具体本章后面会进行介 绍。 25.4.2 SAS Text Miner SAS Text Miner用于发现隐藏在非结构化文档集合中的信息。它会 自动评估和理解来自任何文件目录或基于Web文件的文本,从中提取信 息并揭示其主题和概念。SAS Text Miner支持对多种语言的文本挖掘, 包括简体中文、繁体中文、英语、法语、德语、俄语、日语、韩语等。 SAS Text Miner将非结构化数据结构化,因此,从文本挖掘收集到 的信息可以直接集成到SAS Enterprise Miner项目中。在SAS Enterprise Miner的流程图中可使用SAS Text Miner节点,这样就可以将结构化(定 量)的数据分析与非结构化数据(自由格式文本)分析相结合。这通常 会产生更好的模型,并且能够提供预知业务行为必要的预测性详情。当 SAS Text Miner使用主题和类别标签定义将非结构化数据结构化时,这 些已经变成用数字表示的结构化数据可用于预报、优化等任何SAS分析 技术。 如图25.19所示为在SAS Enterprise Miner中使用SAS Text Miner节点 提取文本主题的示例。 图25.19 在SAS Enterprise Miner进行文本分析 SAS Text Miner还可以与SAS Enterprise Content Categorization集 成,结合自定义的语义规则,提供更精确的文本分析效果。SAS高性能 分析产品SAS High-Performance Text Mining将文本挖掘任务提交到SAS 高性能分析平台上运行,以提高其运行效率。 25.4.3 SAS商业分析解决方案 基于SAS智能平台的技术,SAS提供面向银行、教育、医疗保险、 零售、生命科学、制造业等各行业的解决方案,例如客户管理、企业风 险管理、财务管理和供应链等。下面给出了常见的各行业SAS解决方 案。 1.客户智能 SAS提供了一套完整的客户智能(Customer Intelligence)解决方 案,使用户能够找到最佳盈利增长机会,从而采取最好的营销行动,最 大限度地增加影响力。SAS客户智能提供了营销关键领域全面的解决方 案,从战略和规划,到对数据和分析的洞察,到优化客户互动,最后还 充分利用了在客户体验中所获得的信息。 SAS客户智能解决方案包括:SAS营销运营管理(Marketing Operations Management)、营销自动化(Marketing Automation)、营销 优化(Marketing Optimization)、实时决策管理(Real-Time Decision Management)、数字营销(Digital Marketing)、客户关联分析 (Customer Link Analytics),以及社交媒体分析(Social Media Analytics)等。 2.欺诈和安全智能 SAS欺诈和安全智能(Fraud&Security Intelligence)扩大了SAS能 力,它可提供分析技术给防范金融犯罪的领域,以及公共安全、合规甚 至防范网络犯罪等方面。其利用通用的分析检测和调查组件来处理跨行 业的所有欺诈、不当支付和合规,从而达到安全性目标。SAS欺诈和安 全智能通过全面跟踪欺诈和安全趋势,来早期识别大规模的威胁,使所 采取的对策能够对欺诈和影响安全性的行为产生最大的影响。 SAS提供的欺诈和安全智能解决方案包含SAS反洗钱(Anti-Money Laundering)、欺诈管理(Fraud Management)、欺诈框架(Fraud Framework)、银行业企业金融犯罪(Enterprise Financial Crimes for Banking)、欺诈网络分析(Fraud Network Analytics)等。 3.风险管理 SAS企业风险管理(Risk Management)解决方案旨在帮助高级风险 管理人员及风险部门,通过降低损失、提高资金管理并在整个组织中建 立风险意识文化,来提高财务绩效。SAS风险管理解决方案可确保数据 的一致性和完整性,并且可向监管机构展示系统的精度和控制,以及归 档的审计跟踪,过程透明,同时还有完整的可追溯性,从而减少了合规 的时间和成本。 SAS提供的企业风险管理解决方案包括:High-Performance Risk、 Risk Dimension、银行业信用卡评分(Credit Scoring for Banking)、资 本规划和管理(Capital Planning and Management)、银行业信用风险管 理(Credit Risk Management for Banking)、银行业风险管理(Risk Management for Banking)、保险业风险管理(Risk Management for Insurance)、Enterprise GRC,以及OpRisk VaR等。 4.供应链智能 SAS供应链智能(Supply Chain Intelligence)解决方案可帮助企业 提供开发需求模式、供应网络和运营的能力,并且可提供针对客户服务 需求的独特见解。SAS使企业能够将来自交易供应链管理(Supply Chain Management,SCM)与ERP系统等的所有数据源的数据整合起 来,为客户提供全面的分析和报告,便于更快、更好地做出决策。 SAS供应链智能解决方案包括:需求导向预报(Demand-Driven Forecasting)、库存优化(Inventory Optimization)、保修分析 (Warranty Analysis)、服务备件优化(Service Parts Optimization) 等。 5.绩效管理 SAS提供当前市场上最广泛、最深入的一系列绩效管理 (Performance Management)产品。可以帮助企业充分利用人员、资金 和技术,以实现短期、中期及长期的战略目标,并且可在业务启动时引 入绩效管理以支持持续的过程改进。SAS绩效管理支持价值创造,并使 组织能够以多种方式进行管理从而提高绩效。例如,在不牺牲未来增长 目标的情况下控制成本,了解是什么在驱动成本和利润(或价值),从 而提高灵活性,识别并响应环境、社会和经营风险及机遇。 SAS绩效管理解决方案包括:财务管理(Financial Management)、 成本利润管理(Cost and Profitability Management)等。 25.5 SAS高性能分析 SAS高性能分析(High-Performance Analytics)使用了SAS内存分析 (In-Memory Analytics)、库内(In-Database)处理和网格计算(Grid Computing)技术来实现高性能的分析和处理,使得用传统分析产品需 要花费数天或数周才能做出的决策,在几分钟甚至几秒钟就可以完成。 分析性能的极大提高使组织机构能够充分利用大数据来做决策,不再需 要在速度和精度之间进行选择。SAS高性能分析还能够最好地使用和管 理IT基础设施资源,充分利用大数据,使企业适应未来的增长需要,并 且能提供卓越的可扩展性。 25.5.1 SAS内存分析 SAS内存分析(In-Memory Analytics)使用了分布式架构和多线程 处理,它运行复杂分析计算的处理速度极快。SAS内存分析的分布式架 构使其可根据业务需求进行扩展。 SAS使用内存分析技术操作的范围包括:数据探索、数据可视化和 对数据进行描述性统计,以及使用先进的算法进行建模、对新数据进行 评分。主要产品包括SAS Visual Analytics、Visual Statistics、In-Memory Statistics for Hadoop,以及高性能分析产品和解决方案。 1.SAS Visual Analytics SAS Visual Analytics(可视化分析)是使用了In-Memory技术的基 于Web的产品。SAS Visual Analytics使组织能够快速探索大数据,并从 中识别模式和趋势,从而为将来的分析提供依据。使用SAS Visual Analytics可将SAS的分析能力应用到海量数据中,加强了数据的分析能 力,同时还能快速并可视化地探索数据、创建视图,从而揭示数据的相 关模式,并且可通过报表分享信息。 SAS Visual Analytics主要包括如下组件: ·SAS Visual Data Builder,用来准备数据,能够汇总数据、联合数 据,加强了数据的预测能力。 ·SAS Visual Analytics Explorer,是一个较高的可视化、数据拖放界 面,组合SAS LASR Analytics Server可加速分析计算,从海量数据中分 析、获取价值。 ·SAS Visual Analytics Designer,能够快速创建报表或仪表板,这些 报表和仪表板可以在移动设备或浏览器中进行查看。 如图25.20所示为在SAS Visual Analytics Explorer中对数据进行探索 的示例。 图25.20 SAS Visual Analytics Explorer中创建数据探索 如图25.21所示为在SAS Visual Analytics Designer中设计报表的示 例。 图25.21 SAS Visual Analytics Designer中设计报表 2.SAS Visual Statistics SAS Visual Statistics(可视化统计分析)是SAS Visual Analytics的 插件,通过基于Web的交互式可视化界面,对SAS LASR Analytics Server中的数据快速地进行统计建模(例如多元回归、逻辑回归、变量 分析、GLM及聚类等)和机器学习(例如决策树、随机预测及单纯贝 叶斯等)。SAS Visual Statistics还提供了并行BY分组处理(使用户能够 同时创建多个模型并比较所有的模型),以及查看离群值及影响点等的 功能。 3.SAS In-Memory Statistics for Hadoop SAS In-Memory Statistics for Hadoop利用内存分析技术,提供统一 的编程接口PROC IMSTAT过程步。使用该过程可对SAS LASR Analytics Server中的数据进行快速的探索性分析、统计建模和机器学 习,还可以进行文本分析、建模比较和评分,并能够进行数据管理。 SAS Studio为其提供了交互式编程界面。 4.SAS高性能分析产品 SAS高性能分析产品(High-Performance Analytics Products)允许用 户分析结构化的大数据和非结构化的大量文本数据,其分析速度很快, 而且能获取更加及时的信息。该产品允许用户选择使用复杂的建模技 术,且可执行频繁的模型迭代来获取对数据更加精确的理解,以便做出 准确及时的决策。SAS高性能分析产品包括以下这些: (1)SAS High-Performance Data Mining 允许用户在SAS Enterprise Miner中通过图形化界面使用大数据及更 多的变量开发预测模型来获取对数据精确和及时的理解。 (2)SAS High-Performance Econometrics 允许使用完整的数据,而不是数据的子集开发分析模型,可使用大 数据及更多的变量开发预测模型来获取对数据进行精确和及时的理解。 (3)SAS High-Performance Forecasting 允许使用高可扩展、分布的内存计算架构为数百万的预测产生定制 模型。 (4)SAS High-Performance Optimization 能够通过建模来解决因数据较大或其他特性导致的难于解决或难于 处理的优化问题。因为优化模型具有快速解决问题的能力,所以可以频 繁地执行建模迭代,并使用复杂的分析来获得优化问题的答案。 (5)SAS High-Performance Statistics 允许开发使用大数据及更多的变量开发预测模型来获取对数据精确 和及时的理解。 (6)SAS High-Performance Text Mining 使组织能够根据大文本数据更好地进行选择,从而更好地理解沟通 并创造新价值。 5.SAS高性能分析解决方案 SAS提供了一系列高性能分析解决方案,以利用SAS In-Memory技 术来获取更快的分析速度。目前支持高性能分析的解决方案包括SAS High-Performance Anti-Money Laundering、SAS High-Performance Risk、 SAS High-Performance Marketing Optimization等。 25.5.2 SAS In-Database SAS In-Database将SAS功能扩展到了第三方数据库系统中。当使用 传统方法访问数据库管理系统DBMS中的数据时,SAS会请求 SAS/ACCESS引擎读取需要处理的数据,并将它们返回SAS进行处理, 这时,如果数据较大,则传输时间受网络带宽的影响就会较长。 若使用库内技术,可通过SAS应用将命令和函数发送到第三方数据 库内部执行,这样既避免了数据的额外移动和转换,同时还利用了数据 库本身及所在硬件环境的计算能力。库内处理可以分为多个并行任务在 数据库内执行,每个任务处理一部分数据,所产生的结果最后会进行整 合并返回SAS应用。通常,其执行时间比数据传输到SAS服务器然后再 执行的时间要短得多。 SAS库内的处理功能将SAS解决方案、SAS分析过程和第三方数据 库管理系统集成起来了。其产品运行在各种多线程DBMS以及硬件和软 件设备中,例如Hadoop、Teradata、Greenplum、Netazza、DB2,可以 在数据库中运行数据挖掘模型的评分代码、执行支持的SAS过程、DS2 程序和格式化SQL查询。SAS提供的库内处理产品主要包括以下这些: ·SAS分析加速器(SAS Analytics Accelerator),使其所支持的Base SAS过程能够在数据库环境中执行,例如SORT和SUMMARY过程等。 执行时,这些SAS过程被“翻译”成数据库本地功能在数据库环境中执 行。 ·SAS评分加速器(SAS Scoring Accelerator),可以将SAS Enterprise Miner产生的评分代码导出为函数。所导出的函数可通过SAS 过程执行,或者使用标准的SQL语句从第三方或数据库特定的应用中调 用,该评分过程在数据库环境中进行。 ·SAS库内代码加速器(SAS In-Database Code Accelerator),可以 将DS2程序发布到数据库中,并在数据库中并行执行。 25.5.3 SAS网格计算 SAS网格计算(Grid Computing)可以将SAS任务分布到网络上的 多台计算机环境上,所有这些计算机都在SAS网格管理器(Grid Manager)的控制下。在这种环境中,工作负载会在计算机组成的网格 集群中分布,可实现负载平衡、提高任务处理速度,还可以进行作业调 度。 SAS网格管理器为运行在共享网格环境中的SAS产品和解决方案提 供了负载均衡、策略执行,资源有效配置、任务优先化,以及实现具有 高可用性的分析环境。它还将SAS应用与执行SAS应用的基础设施分 开,能够根据需要添加或删除硬件资源,并提供了网格基础设施内的硬 件故障容错。SAS网格管理器还集成了Platform Suite for SAS的资源管理 和调度功能。 如图25.22所示为SAS网格计算的拓扑结构。 1.网格控制服务器(Grid Control Server) 该机器控制到网格的作业分发。网格中的任何机器都可以被指定为 网格控制服务器。此外,也可以选择是否将网格控制服务器配置为能够 接收工作的网格资源。这台机器必须包含Base SAS、SAS/CONNECT和 Platform LSF,通常还要包含Platform Process Manager和Platform Grid Management Services(GMS)。网格控制服务器也可以配置SAS工作区 服务器,使SAS应用(SAS Data Integration Studio、SAS Enterprise Miner、SAS Enterprise Guide和SAS Add-in for Microsoft Office)可以运 行利用网格的作业。 图25.22 SAS网格计算拓扑结构 2.网格节点(Grid Node) 这些机器是网格计算资源,能够接收分发到网格上的工作。所需要 的网格节点的数目取决于该网格中运行的作业之大小、复杂性和作业数 量。可以根据业务需求添加或删除节点。每个网格节点必须包含Base SAS、SAS/CONNECT、Platform LSF,以及运行支持网格的作业所需要 的任何应用程序和解决方案。 3.中央文件服务器(Central File Server) 该机器用来存储在网格上运行的作业所需要的数据。为了简化安装 和易于维护,也可以在中央文件服务器上安装SAS。 25.6 本章小结 SAS智能平台提供了端到端的基础架构平台,实现数据的获取、处 理和管理、分析和信息的创建、管理和分发等。在该智能平台的基础 上,SAS还提供了以下产品: ·商业智能产品,向用户提供报表、查询、多维分析以及深入的统 计分析等功能。 ·数据管理和集成平台,实现连接、获取、存储和管理数据,并提 供一致、准确、可靠的数据访问。 ·数据挖掘和文本挖掘产品,满足对结构化和非结构化数据的复杂 挖掘建模需求。 ·面向各个业务领域的商业分析解决方案,解决面向特定领域的业 务问题。 ·高性能分析产品,提供对大数据和大规模运算的支持。 第26章 SAS应用的架构规划 上一章介绍了SAS智能平台、解决方案,以及高性能分析软件及其 功能。对于SAS项目的实施人员而言,不但要了解SAS产品各个模块的 具体功能,而且要具备从架构体系角度规划SAS应用的能力。 本章介绍了SAS应用的各种主要架构和配置选择,包括对存储和I/O 系统的必要考虑,以便SAS方案架构师和相关的IT架构师在规划和构建 SAS应用时参考。 26.1 SAS应用的架构规划 SAS智能平台是SAS解决方案的基础。本节介绍SAS智能平台的基 本架构,以及为了提高应用的可用性而在该基本架构上实施的扩展。此 外,还将介绍SAS Grid Manager、SAS库内分析和SAS内存分析等SAS 现代化(Modernization)系统架构。 26.1.1 SAS应用的架构 第25章的图25.1给出了SAS智能平台基础体系架构,主要包括:数 据源、SAS服务器、中间层以及客户端。该架构使用了n层结构的设计 理念,可支持快速扩展以符合工作负载的需要。例如,可以将各层组件 部署在不同的物理机器上,也可以对各层组件进行高可用和/或负载均 衡配置,此外,SAS中间层还可以与其他第三方技术或产品进行集成。 本节只从体系架构的角度对SAS的元数据服务器集群、计算服务器 负载均衡和SAS Web Application Server集群进行介绍。第28章会更全面 地介绍SAS智能平台各组件的高可用性和负载均衡特性。 1.基本架构 对于大型企业和组织机构,SAS智能平台的各层可以在不同操作系 统的多台机器上安装,这种部署拓扑称为多机部署。在小型企业中,或 者是为了进行原型设计、演示,所有的各层可以安装在同一台机器上, 该部署称为单机部署。 (1)单机部署 单机部署指将SAS服务器(包括元数据服务器和计算服务器)、中 间层、客户端均部署在同一台机器上。采用这种部署意味着,用户必须 登录服务器所在的物理机器才能使用SAS。而且,当SAS所部署的物理 机器上运行的操作系统为UNIX时,SAS Enterprise Guide等只能运行于 Windows操作系统下的客户端是无法安装部署的。如前所述,单机部署 一般用于原型开发、概念验证与演示等目的,很少用于商业的生产环 境。 (2)两层部署 两层部署,即将SAS客户端与其他组件(SAS服务器和中间层)分 别部署在不同机器上。可以安装多个客户端,用户通过客户端完成管理 任务和分析工作,例如用户管理、提交SAS作业(或SAS程序)到SAS 服务器运行等。当选择这种部署时,SAS服务器和中间层可以安装在 UNIX操作环境上,而客户端则安装在Windows操作环境上。这样,用 户可以使用Windows环境下的SAS Enterprise Guide、SAS Data Integration Studio等客户端将SAS作业提交到远程的SAS服务器进行分析 处理。这种部署架构在小型组织机构中或部门级的应用中较为常见。另 外比较常见的应用场景就是在项目的开发阶段,用于开发和测试。 (3)三层部署 在上面提到的两层部署中,SAS服务器和中间层共享着硬件资源。 在SAS应用达到一定的规模后,计算服务器和中间层可能会竞争单台机 器的硬件资源,影响系统的整体性能。这时,可将SAS服务器和中间层 分开部署。这种部署称为三层部署,即SAS服务器、中间层以及客户 端。这种部署架构是比较经典的应用方式,常用于中大型组织机构中。 (4)四层部署 在三层部署中,SAS元数据服务器和SAS计算服务器共享硬件资 源,当SAS计算服务器工作负载较高时,就可能会影响对元数据服务请 求的响应性能。元数据服务是SAS系统的关键服务,元数据服务器的响 应时间会直接影响整个系统的性能。这时可以将SAS元数据服务器和 SAS计算服务器分开部署。这种部署称为四层部署,即:元数据层、 SAS计算层、中间层和客户端。对系统性能要求较高的应用,建议采用 四层部署。 上述部署分层部署可通过SAS的安装计划进行设置。当实际应用对 高可用性或分析处理能力有更高的要求时,可在上述基础体系架构上进 行扩展。 2.元数据服务器集群 从SAS 9.4开始,SAS提供了对元数据集群配置的支持。SAS元数据 集群(SAS Metadata Server Clustering)最少包含3个节点,如图26.1所 示。注意,元数据备份目录必须设置在3个节点能够同时访问的共享文 件系统中。 图26.1 SAS元数据集群 通常,这3个节点中首先启动的节点为主节点,其他节点为从节 点。主节点根据轮询算法将连接请求分配到各个从节点。在3节点的元 数据集群中,当其中的一个节点发生故障时,连接到发生故障的节点的 连接会自动迁移到其他活动的从节点中。 如果对系统的可用性有较高的要求,建议首先采用此架构体系部署 元数据集群。 3.计算服务器负载均衡 SAS提供了计算服务器的负载均衡机制。SAS平台实施人员或SAS 管理员可以在多台机器上配置SAS计算服务器,并启用其负载均衡功 能。支持内置负载均衡的SAS服务器包括SAS Workspace Server、SAS Pooled Workspace Server、SAS Stored Process Server和SAS OLAP Server。在该负载均衡配置中,会根据指定的负载均衡算法,将来自客 户端的请求分发到不同机器对应的计算服务器上。 如图26.2所示为在两台机器上均配置上述4种服务器并为其实现负 载均衡的示例。当然,也可以根据业务需要只配置其中部分SAS计算服 务器的负载均衡。在配置SAS Workspace Server、SAS Pooled Workspace Server和SAS Stored Process Server中的任何SAS服务器负载均衡时,都 必须配置SAS Object Spawner,因为这些服务器的负载均衡机制均由 SAS Object Spawner管理。 同时该架构还具有很好的可扩展性。可以在更多的机器上进行类似 的配置,并启用这些服务器的负载均衡机制,以便工作负载可以分发到 更多的硬件机器上。 如果对计算服务器的可用性有较高要求,或者计算服务器的负荷较 大(特别是并发用户或会话数较大时),建议采用该架构。 4.SAS Web Application Server集群 SAS Web Application Server支持垂直集群和水平集群(参考28.5.1 节)。这里讨论的垂直集群,指在多个机器上配置多个同等的Web Application Server实例,并将SAS Web应用程序部署在这些实例中。默 认配置下,SAS Web Server会将进入的请求分发到不同的Web Application Server实例中进行处理,从而实现对SAS Web应用程序的负 载均衡。 图26.2 SAS计算服务器负载均衡 在配置SAS Web Application Server集群时,可以将SAS Web Server 配置在单独的机器上,SAS Web Application Server的多个实例则分别配 置在不同机器上,如图26.3所示。 图26.3 SAS Web Application Server集群 当预计SAS Web Application Server会成为系统性能的瓶颈,例如前 端报表查询的并发用户数庞大时,或者期望该部分具备高可用性时,建 议采用此架构。 5.SAS中间层集成规划 SAS的中间层也可以与其他软件进行集成,例如配置TLS、Web认 证、IWA,以及与现有反向代理(Reverse Proxy)集成等,主要是安全 管理方面的。关于SAS智能分析平台的安全性更详细的信息请参考本书 第27章。当对系统的安全性有较高要求,或者需要与已经存在的其他系 统集成时,需要考虑这些架构方案。 (1)配置TLS 传输层安全协议(Transport Layer Security,TLS)是SSL的后续协 议,用来提供网络安全和隐私。TLS除了提供加密服务以外,还使用受 信任的证书来执行客户端和服务器身份验证,并使用消息认证码来确保 数据的完整性。 可以在使用SAS Deployment Wizard初始部署时选择启用TLS协议。 这时,SAS Deployment Wizard会自动将SAS Web应用程序配置为通过 HTTPS进行访问。如果在初始部署时未选择启用TLS协议,也可以在 SAS安装部署完成后手动进行配置。 (2)配置Web认证 默认情况下,SAS Web应用程序使用由SAS Logon Manager提供的 基于表单的身份验证。凭证提供给SAS Logon Manager之后,又被发送 到SAS元数据服务器。然后,元数据服务器到身份验证提供方来验证该 凭证。默认的身份验证方是主机操作系统。 可以将SAS Web应用程序配置为在中间层进行身份验证,即Web认 证(Web Authentication),当用户登录到SAS Web应用程序时,由SAS Web Application Server处理初始身份验证。 实现Web验证可促进SAS Web应用程序的单点登录。当中间层配置 为Web认证时,用户第一次登录SAS Web应用程序时需提供认证凭证, 但接下来访问其他Web应用程序时就不需要重新进行身份认证了。 (3)配置IWA 在用户拥有Windows域账户的环境中,可使用集成Windows验证 (IWA)。启用IWA后,用户在访问应用程序时,应用程序不会提示要 求提供用户名和密码。客户端计算机上的当前Windows用户信息由Web 浏览器通过哈希密码交换提供给SAS Web应用程序服务器。 在中间层中,IWA的关键组件是Active Directory控制器、 KDC(Key Distribution Center,Kerberos密钥分发中心)、客户端浏览 器和SAS Web应用程序服务器。Kerberos是一种用于验证用户或主机身 份的行业标准认证协议。Kerberos协议使用强加密,客户端可以跨不安 全的网络连接向服务器证明其身份,反之亦然。使用Kerberos协议有如 下要求: ·客户端必须能够直接连接到Active Directory。 ·客户端和服务器必须都能受信连接到KDC,而且与Active Direcotry 兼容。 可以在使用SAS Deployment Wizard初始部署时选择启用IWA,也 可以在SAS初始部署完成后再进行配置。 (4)与现有反向代理集成 当企业的网络拓扑已经存在于Web服务器中,且用于代理连接时, 可以重新配置SAS中间层,并使SAS中间层与现有的Web服务器进行交 互。这时,最简单的配置方式是仍然保留SAS部署中的SAS Web Server。这样一来,当SAS Web应用程序服务器配置为集群时,SAS Web服务器可以继续对到集群的连接请求进行负载均衡。 (5)与第三方安全管理软件集成 SAS中间层可以与IBM Tivoli Access Manager WebSEAL、CA Siteminder等安全管理软件集成,以将SAS Web应用集成到企业已有的 安全系统中。 IBM Tivoli Access Manager WebSEAL可保护Web应用程序不被未授 权使用,并且可实现单点登录。当SAS Web应用程序部署在该环境中 时,SAS Web应用程序也可以利用WebSEAL提供的架构来处理包括认 证用户和控制资源访问等的安全性,使SAS软件部署符合企业现有的安 全环境。 CA Siteminder是集中式Web访问控制管理系统,它提供了集中的 Web单点登录、认证管理、基于策略的授权、身份联盟和审计等服务。 SAS的Web应用可以部署在CA Siteminder管理的环境中。通过配置代理 Web服务器(可以是SAS Web Server,也可以是第三方的代理服务器) 和SAS Web Application Server,可以实现SAS与CA SiteMinder集成,从 而实现由CA Sitminder统一管理的包括SAS Web应用的单点登录、认证 管理等功能的企业安全系统。 (6)SAS认证API SAS认证API提供了将用户开发的应用程序与SAS Web应用程序集 成的方法。使用该组API可实现从已经验证过的自定义应用程序访问 SAS Web应用程序时,不必交互式地输入用户名和密码。 26.1.2 SAS Grid Manager架构 SAS Grid Manager是由多台机器组成的网络计算环境。可将其中的 一台机器作为网格控制器,其他机器作为网格节点,网络节点可以有多 个。如图26.4所示为SAS Grid Manager的SAS网格环境架构示意图。 图26.4 SAS网格环境架构 SAS客户端可将SAS作业和任务发送到多个网格节点进行处理。在 SAS网格环境中,需注意以下两点: ·在网格控制器和网格节点上需安装Platform LSF软件、Base SAS、 SAS/CONNECT,以及在该节点进行分析和计算需要的其他SAS软件。 SAS数据、SAS作业部署目录等都需要存放在共享文件系统中。 ·当对系统的SAS服务器层有高的可用性要求,或者需要具备并行处 理能力并且处理任务之间具备较好的逻辑独立性时,这种架构是很实用 且有效的一个选择。 26.1.3 SAS库内产品架构 SAS库内产品将SAS的分析、评分以及数据质量等功能放在第三方 数据库系统中运行,可避免数据在网络上迁移,从而提高性能和安全 性。其主要产品包括:SAS分析加速器(SAS Analytics Accelerator)、 SAS评分加速器(SAS Scoring Accelerator)、SAS数据质量加速器 (SAS Data Quality Accelerator)和SAS代码加速器(SAS Code Accelerator)等。这些产品的架构相同,如图26.5所示。 图26.5 SAS库内产品架构 该架构中的元数据层、计算层和中间层与SAS智能分析平台没有差 别。主要差异在于,该架构会在第三方的数据库系统中安装SAS库内分 析产品组件。Base SAS软件或SAS Workspace Server等并不承担真正的 分析任务,而是将分析任务转换成该数据库系统可识别的语句,并发送 到数据库中执行。 如果数据已经存在于第三方数据库系统中,想充分利用这些数据库 系统的计算和存储能力,减少数据的传输开销,则可以考虑SAS库内产 品架构。 26.1.4 SAS内存分析产品架构 SAS Visual Analytics、SAS Visual Statistics、SAS In-Memory Statistics for Hadoop、SAS High-Performance Analytics等SAS产品或解决 方案都是基于SAS的内存分析技术。 SAS内存分析分为非分布式模式和分布式模式。根据计算发生的位 置与数据存储的机器是否相同,分布式模式在架构上又分为Co-located 模式和SAS RACK选项。 在期望获得高性能时,建议采用本架构。具体而言,当系统需要处 理大数据时,或者运算量庞大时,例如对大型的优化问题求解,建议采 用本架构的分布式模式。对于小规模数据或运算量,如果想得到较高的 性能,例如较短的数据分析时间或较大的并发用户数量,也可以考虑非 分布式(Non-distributed)模式。 1.非分布式(Non-distributed)模式 无论是非分布式还是分布式部署架构,其中的客户端、元数据层、 计算层及中间层与传统的SAS Intelligence Platform部署架构相同。在非 分布式模式下,同一个分析任务只在一台SAS服务器上进行。在传统的 SAS产品中SAS分析处理任务大多是单线程执行的,不过,在SAS内存 分析产品中启用了多线程模式,这样就可以同时利用SAS服务器所在机 器上的多个CPU进行分析处理了,从而极大地减少了运行时间。 2.分布式(Distributed)Co-located模式 SAS内存分析产品的分布式部署更为常用,尤其是在分布式存储 (如Hadoop)越来越成为主要趋势的现今。在分布式部署中,SAS计算 服务器会将分析处理指令提交到高性能分析集群,然后由高性能分析集 群的多台机器并行执行。 对于分布式存储的数据,SAS内存分析产品可以利用其所在的机器 进行分析处理,即数据所在的机器充当高性能分析集群的角色,如图 26.6所示,这种模式称为Co-located模式。当用户通过Base SAS或SAS Workspace Server等计算服务器提交任务时,SAS首先将数据从所在的机 器存储并行加载到各自的内存中,然后由该机器的CPU进行并行分析处 理。各机器节点之间使用MPI(Message Passing Interface,消息传递接 口)进行通信。 图26.6 SAS内存分析分布式Co-located模式 3.SAS RACK选项 启用SAS RACK选项后会使用独立的高性能分析集群,如图26.7所 示。当用户通过Base SAS或计算服务器提交分析任务时,数据会从所在 的数据存储集群环境中通过SAS Embedded Process组件并行加载到另外 一个独立的高性能分析集群的机器(或节点)的内存中,分析任务由这 些机器的硬件资源完成。与分布式模式Co-located相同,其各高性能分 析集群节点之间也使用MPI进行通信。 图26.7 SAS内存分析SAS RACK选项 这种架构避免了SAS分析任务与数据存储业务之间竞争资源,但是 要求配置额外的硬件,此外,在数据存储集群和SAS高性能分析集群之 间还要具有最大的网络带宽。如果数据存储集群的工作负荷已经很大, 或者数据存储集群需要向多个数据分析应用提供数据,那么这种架构是 一个理想的选择。 26.1.5 SAS部署在高可用集群中的架构 当对应用的可用性有较高要求时,除了前面讨论的SAS元数据集 群、计算服务器的负载均衡、SAS Web Application Server集群和SAS Grid Manager等架构外,还可以考虑将SAS部署在高可用集群中。 高可用集群是指将高可用性软件安装在多台计算机上,并通过适当 的配置将这些计算机组建成一个集群,该集群提供对应用程序或服务的 保护以获得高可用性,是独立于具体的SAS技术的架构。通过适当的配 置,可以将SAS应用部署在高可用集群中,以便对SAS应用中的关键服 务器提供故障转移保护。关于SAS高可用集群更为详细的信息请参考本 书第28.2.1节。 图26.8给出了将SAS组件部署在高可用集群中的架构示意图。 该架构有如下特点: ·高可用软件安装了在所有节点上,并将这些节点配置为高可用集 群。 ·SAS组件安装在高可用集群的节点上。为了保持SAS配置同步, SAS配置文件需要配置在两台机器可访问(不必同时访问)的共享存储 中。如果各节点操作系统为UNIX,SAS可执行文件也可以同时安装在 共享存储中。 ·需要配置企业DNS或使用F5等相应功能的硬件设备,以实现在故 障转移时IP地址或逻辑机器名的漂移。 ·为了让两节点的高可用集群正常工作,还需要配置仲裁设备。根 据不同的高可用软件,仲裁设备可以是共享磁盘分区或共享文件系统。 ·有些高可用集群还要求配置隔离设备或隔离软件。当提供服务的 节点发生故障时,隔离设备或软件可将发生故障的节点与其他依赖的资 源隔离。 图26.8 SAS部署在高可用集群中 请参考对应的高可用软件安装和配置文档以及SAS的相关文档获取 更为详细的配置信息。 26.2 SAS应用的I/O系统规划 在对SAS应用的架构进行规划时,底层的硬件规划非常重要,其 中,存储和I/O的规划又尤为重要。本节提供了SAS常规工作负载的I/O 特性,以及设置SAS应用所在环境文件系统的通用指南。 26.2.1 SAS应用的I/O特性 理解SAS应用和不同工作任务与I/O系统交互的特性是规划SAS环境 存储架构的关键步骤。 SAS环境通常由执行多并发SAS进程的用户工作负载组成,这些并 发进程可以是批处理的作业或会话,也可以是交互式的作业或会话。这 些SAS进程通常涉及大量的数据,其处理和访问模式与在线交易处理系 统非常不同。一般来说,SAS会话执行大量的顺序读写操作,不过,有 时SAS应用程序也执行一定量的随机数据读写。 然而,并发的SAS会话访问同一数据文件时,尽管单个会话是顺序 访问的,但是在操作系统看来,所有SAS会话的访问都可能是随机的。 依赖于并发会话数,按照随机访问调节I/O系统比按顺序访问调节I/O系 统更有效,比如,大量并发进程通常会减少文件缓存效率。 SAS应用程序和工作负载的特性总结如下: ·SAS应用会创建许多临时文件。存储子系统和操作系统需要支持创 建许多小的临时文件和大的顺序访问的临时文件。在一个作业中,这些 文件在被创建后,可能会有多次更新或重命名,并且在作业运行结束前 会被删除。文件的大小范围可能从非常小(大约1KB)到非常大(超过 1TB的大小)。临时文件的大小在创建时是未知的。 ·SAS I/O不使用预分配存储。不同于传统RDBMS应用所使用的预 分配策略,SAS应用程序必须创建自己的扩展。在基于扩展卷的文件系 统中,SAS创建文件时会分配少量的存储,但随着文件增长,SAS需要 新的存储空间。可能的解决办法是使用允许配置大的固定扩展的文件系 统。然而,从未使用空间来看,这可能是浪费的,但是当处理大的对象 时,浪费率应该很小。 ·SAS为其数据存储(SAS数据集、索引)创建了标准的操作系统文 件。默认情况下,SAS访问文件时使用文件锁。对于有些文件系统,尤 其是NFS,使用文件锁会禁用缓存、预读和延迟写。 ·SAS执行写入操作时,每个SAS会话有一个单独的写线程。SAS执 行读取操作时,有些SAS任务支持多线程并且可启动多个读线程。 26.2.2 SAS文件系统考虑 本节提供了对基础的SAS应用程序要求的文件系统进行设置的通用 指南。需要说明的是,各种文件系统的配置也依赖于该SAS应用所实现 的具体工作内容和应用底层的数据模型。 针对上述SAS I/O特性,通常推荐配置最少两个存储空间来支持 SAS:一个存储永久SAS数据文件,一个存储临时SAS数据文件(SAS WORK和UTILLOC)。默认情况下SAS WORK和UTILLOC处于同一文 件系统,也可以将它们分别配置在不同文件系统中。如果预算许可,可 将SAS WORK和UTILLOC置于有足够带宽和可高速读写的高性能存储 中,这将有助于提高系统的性能。然而,对大多数现代存储系统来说, 这些文件系统通常会共享底层物理资源。在现在的存储系统中,大多数 磁盘存储阵列会配置为“全条带化”系统,逻辑单元(LUN)则会跨存储 子系统中的大多数或全部磁盘进行条带化。 SAS系统管理员应该清楚地知道SAS应用程序的工作负载和I/O特 征。当SAS应用程序与其他非SAS应用程序共享物理环境时,必须持续 监控以确保SAS应用程序的文件系统资源不被其他应用程序争夺。 针对SAS应用中不同的数据和文件,可以考虑如下建议: 1.根操作系统 根操作系统是存放操作系统和交换文件的位置。建议使用 RAID1(镜像)保证该关键文件系统的高可用性。 2.SAS软件可执行文件 可存放在根操作系统所在的文件系统上,建议使用RAID1。 3.永久SAS数据 永久SAS数据通常指SAS应用中的永久SAS数据文件和原始的输入 数据源文件,是SAS数据集市、数据仓库的重要组成部分。存放这些文 件的存储位置在SAS作业开始时,会是繁重的“读”区域,在作业结束写 入结果文件,或刷新数据集市或数据仓库时又会是繁重的“写”区域。 大多数SAS用户希望将永久SAS数据放置在冗余文件系统中,以保 证SAS数据的可用性。RAID10通常会提供最好的冗余和性能,但是镜 像要求使用更多的存储空间来支持相同的文件空间。大多数存储管理员 会使用RAID5降低额外的空间成本,同时保持校验和提供的冗余。 4.SAS WORK和UTILLOC SAS会话的临时工作区。在该文件系统中可能会有繁重的顺序读写 活动。依赖于SAS任务,通常可能会读写许多小文件或几个大文件。写 入SAS WORK中的文件仅在SAS会话期间可用,当SAS会话正常终止时 会被擦除。主要的I/O活动可能都发生在该文件系统中。UTILLOC是指 定用多线程的应用程序来存储实用文件的文件系统,默认为SAS WORK。这些实用文件相关的I/O压力可通过指定UTILLOC参数将实用 文件的位置指定到不同的文件系统来缓解。 该文件系统中创建的文件是临时的,而且在SAS会话运行结束后也 不需要再次访问,可以配置为RAID0。但是,现在的客户也会要求SAS WORK文件系统的高可用性,这时可考虑使用RAID10。同时,RAID5 也是非常常用的配置。 注意 上述RAID配置在闪存(Flash)阵列中可能会不同。本章 不讨论闪存阵列的配置。 在创建标准操作系统文件系统时,需要选择要运行的SAS应用程序 最适合的文件系统。如果应用程序工作负载会有繁重的顺序写活动,通 常不推荐使用NFS文件系统。因为NFS不支持文件锁。此外,当跨网络 访问文件时,网络可能影响SAS性能。如果SAS应用程序工作负载以繁 重的顺序读写为主时,则可根据操作系统选择如下文件系统: ·Linux RHEL–XFS ·Windows–NTFS ·AIX–JFS2 ·Solaris 10–JFS ·HP-UX–JFS 在设置文件系统时,确保启用预读(Read-Ahead)和延迟写 (Write-Behinds/Write-Through)。 如果在服务器集群上运行SAS,例如SAS网格环境中,则需要确定 集群或网格中的各个节点是否需要共享文件系统。如果需要,则可使用 集群文件系统(Clustered File System),以达到共享存储和集群中每个 节点之间的最大I/O带宽。 26.3 本章小结 不同SAS产品或解决方案的架构对底层的I/O系统的要求可能会有所 不同。产生影响的因素包括:是否需要配置高可用或负载均衡集群,产 品中是否包括SAS网格管理器、SAS库内分析产品、内存分析产品等。 SAS方案架构师和相关的IT架构师在规划SAS应用时需要对这些因素进 行考虑并细致规划。 第27章 SAS智能平台安全管理 SAS智能平台提供了包括认证、授权、加密、审计等在内的安全性 功能。该智能分析平台可以与主机环境、Web领域(realm)、第三方数 据库系统的用户管理功能协作,提供集成的安全性身份认证和数据访 问,并且可以与第三方安全性产品集成,以提供安全保障功能。本章从 SAS身份(用户和组)开始,介绍SAS智能平台对用户进行认证和权限 管理的功能。此外,还介绍如何对传输中(in transit)的数据和静态 (at rest)数据进行加密,以及如何进行审计等。 27.1 身份标识 为了保证访问的唯一性并追踪用户行为,系统必须知道是谁在做出 请求。在SAS智能平台中,对于每个注册过的用户,至少有一个用户 ID(主机、Windows活动目录或LDAP中的用户ID)被存储在SAS元数 据中,即在SAS元数据中会存储每个用户的外部账号ID。SAS使用这些 账号ID建立唯一的SAS身份。所有的用户成员关系、权限 (permission)和权力(capability)都与用户的SAS身份完全绑定。 SAS Management Console的插件“用户管理器”窗口显示了SAS环境 中的所有SAS用户、组和角色,如图27.1所示。 可以通过“用户管理器”窗口中的复选框“显示用户”、“显示组”、“显 示角色”来选择列出或不列出相应的类别。此外,管理(添加、删除、 编辑)用户、组或角色也是在该窗口完成的。图27.2中给出了通过浮动 菜单新建用户、组或角色的示例。 27.1.1 用户 用户(User)是个人或服务的SAS身份(Identity)。推荐为每个使 用SAS环境的人创建单独的SAS身份,这样就能够在元数据层为每个用 户设置单独的访问权限,同时也可以为每个用户建立相应的个人文件 夹。 图27.1 用户管理器 图27.2 新建用户、组或角色 通常,建立SAS身份需要通过以下两个域中的身份互相配合来完 成: ·认证提供方所提供的账号ID。 ·元数据中的定义,该定义中包括了上述账号ID。 根据认证提供方的不同,SAS用户分为常规SAS用户(也称为SAS 用户)和内部账号,其认证分别由外部认证系统和SAS元数据提供。 1.SAS用户 在初始部署完毕的SAS环境中,SAS管理员(组)拥有用户管理角 色,该组的成员可执行大部分的用户管理任务。SAS管理员可以在SAS Management Console中通过用户管理器插件创建SAS用户,如图27.1所 示。 创建SAS用户时通常需要用到外部认证系统中的账号ID。在最简单 的配置中,每个SAS用户需要在元数据服务器主机上拥有已知的账号。 当元数据服务器操作系统为Windows时,用户通常在活动目录(Active Directory,AD)中拥有账号;如果操作系统为UNIX,用户通常有 UNIX账号。可以基于这些账号创建SAS用户。此外,还可以配置并创 建使用LDAP账号的SAS用户等。 在“新建用户”对话框的“账户”选项卡中,需要将外部系统中的账号 和所创建的用户绑定。图27.3在新建用户sasabc时为其提供在Windows域 corpdomain中的账号。 图27.3 用户外部账号 在“新建用户”对话框的“常规”选项卡中,还可添加该用户的联系信 息,例如电子邮件、电话、地址,这些信息可用于配置邮件通知等功 能。 用户的密码在认证提供方设置,通常在SAS元数据中不保存密码。 比如,对于图27.3中建立的用户sasabc,当其登录时,会通过元数据所 在主机到定义该用户时指定的Windows域中去认证,在SAS元数据中不 需要保存密码。但是,如果要对某个服务器进行无缝访问(不需要交互 式地提供用户名和密码),而且该服务器要求的凭证(Credential)与用 户初始提交的凭证不同,则需要在该用户的SAS元数据中保存密码。例 如,当访问第三方数据库服务器或在一个多平台环境中访问标准工作区 服务器时,就需要在用户的账户中添加对应服务器合适的用户名和密 码。否则,一些应用程序可能会给出提示对话框,要求输入用户名和密 码。 2.内部账号 内部账号的认证提供方为SAS元数据服务器。内部账号仅存在于 SAS元数据中,其形式为name@saspw,常用于元数据管理和运行一些 服务。例如,内部账号“SAS管理员”的形式为sasadm@saspw,“SAS信 任用户”的形式为sastrust@saspw。 内部账号通常是通过SAS Management Console的用户管理器创建 的,其创建方式与创建常规SAS用户类似。图27.4为SAS管理员的账号 信息。注意,在SAS 9.4中,SAS管理员还用于对SAS Web Infrastructure Platform数据库中的数据进行访问,所以其账号信息中还会包括访问该 数据库的凭证。 图27.4 SAS 9.4中的SAS管理员账户 内部账号的使用存在一些限制。例如,仅有内部账号的用户(可为 一个用户设置多个账号)不能用于访问SAS工作区服务器,所以SAS内 部账号通常不用作常规SAS用户。 内部账号的定义中包括密码信息。有些账号的密码还被存储在一些 配置文件中,例如SAS管理员和SAS信任用户的密码。 在SAS的默认配置中,内部账号在登录时如果连续3次输入错误密 码会导致该账号被锁住一个小时。用户可以选择等待一个小时后再使用 该内部账号,或者解锁该账号。解锁内部账号的具体步骤请参考SAS Intelligence Platform:Security Administration Guide。 27.1.2 组 组(Group)是用户的集合,常用于访问控制设置并简化安全管 理。在SAS智能分析平台的初始配置中会创建一些预定义的SAS组,如 SAS管理员组、SAS系统服务组、SASUSERS组、PUBLIC组等。这些组 的特性如下: ·SAS管理员组是元数据管理员的标准组。在标准配置中,该组为其 成员提供了大多数管理性的权力。 ·SAS系统服务组使其成员(SAS信任用户默认为其成员)能够列出 并使用SAS服务器、OLAP立方体以及其他SAS对象。 ·SASUSERS组自动包括拥有个人身份的用户,即在元数据中定义 过的用户。 ·PUBLIC组自动包括能够访问元数据服务器的所有人,不论是直接 访问的还是通过受信关系访问的。没有个人身份的用户(即未在元数据 定义的用户)仅仅有PUBLIC组身份。 大部分预定义的组有的太泛(例如SASUSERS),有的拥有太高的 权限(SAS管理员),所以经常要根据实际业务需要创建自定义的组。 与创建用户类似,组通常也是通过SAS Management Console的用户管理 器来创建的(如图27.2所示)。注意,组使用的账号不能为内部账号。 一般情况下,大多数组不包括登录信息,除非希望通过组来创建多个用 户的共享凭证。 定义SAS组常用于如下目的: ·管理不同访问的分类权限。可以创建组(例如为每个业务单元或 职责功能域的用户创建一个组),为该组设置访问权限和角色(角色概 念在下一小节中介绍),再将具有相同安全设置需求的用户加入该组 中。该组中的所有成员具有为该组设置的访问权限以及该组所属角色所 具有的能力。这比为多个用户分别进行安全设置更简洁有效。 ·多个用户使用共享凭证。这时通常为该组设置外部账号ID和密 码,以使该组的所有用户都可以使用该凭证访问SAS服务器或第三方数 据库。 27.1.3 角色 SAS通过角色(Role)来管理用户是否可以使用SAS应用程序的某 些功能,例如SAS应用程序的菜单项功能等。基于角色管理的应用程序 之功能称为“权力”(Capability)。当某个用户属于某个角色时,该用户 就拥有该角色的权力。角色确定当用户使用应用程序时可以看到和使用 哪些用户界面元素,例如菜单和插件。 每个支持角色的SAS应用程序都提供了预定义角色,每个预定义角 色都有唯一的初始权力集合。角色提供的权力反映了该角色成员的活动 和职责。同样,可以在SAS Management Console中,通过用户管理器添 加、删除角色,同时,它还具有查看、编辑角色的权力。以预定义角 色“Enterprise Guide:分析”为例。在“用户管理器”窗口选择角 色“Enterprise Guide:分析”,右键选择“属性”。在打开的“‘Enterprise Guide:分析’属性”对话框中,选择选项卡“权力”,即为以树状结构显示 该角色的权力,如图27.5所示。勾选的项表示属于该角色的成员拥有对 应的权力,未勾选表示不拥有。 可以将用户添加为某个或某些角色的成员,也可以通过改变用户所 属角色的权力来设置或调整用户权力。例如,通过增加或清除所属角色 的初始权力,或者创建新角色并为该角色提供权力组合,然后将用户添 加为该角色成员等方法来设置或调整用户权力。权力是累加的,用户所 拥有的权力是其所属角色的所有权力。 注意,设置角色和组的目的并不相同。组用于管理访问权限,而角 色用于管理权力,具有某种权力并不表示符合权限要求。例如,可以看 到并使用某菜单(通过角色控制)并不意味着使用该菜单可以查看报表 或数据(通过权限控制)。 图27.5 SAS角色和权力 27.2 认证 认证(Authentication)是用户向系统证明他就是自己所声称的用户 (即被许可的用户)的过程。用户在登录SAS智能分析平台访问SAS服 务器和第三方数据服务器时,通常首先需要对用户进行身份认证,并在 访问服务器时使用相应的身份信息。SAS提供了多种身份认证机制,可 以在SAS环境中组合使用,以满足和平衡企业对于安全性的需求,例如 保护个人身份、降低安全风险、提供统一的用户体验、在一个环境中提 供对不同系统的访问,以及和各种通用计算环境进行集成等。 27.2.1 认证机制 SAS支持多种认证机制,根据认证提供方、进行认证的方式,以及 要访问的目标服务器来分,包括SAS内部认证、主机认证、SAS令牌认 证、LDAP集成认证、集成Windows认证等。 1.SAS内部认证 SAS内部认证是指直接由SAS服务器进行验证,而不需要使用外部 认证提供方。这种机制用于验证ID后缀为@saspw的用户,即内部账 号。 该认证机制主要用于连接到元数据服务器,同时也支持直接连接 OLAP服务器,但后者不常见。 2.主机认证 主机认证(Host Authentication)是指由客户端提供外部用户的ID和 密码给SAS服务器,SAS服务器将该凭证传递给服务器所在的主机进行 认证。 该认证机制主要用于直接连接到SAS元数据服务器、OLAP服务 器,以及工作区服务器。 3.SAS令牌认证 在每次认证时,先由SAS元数据服务器产生身份令牌,客户端再将 该令牌发送到目标服务器进行认证。该方法会使目标服务器接受连接到 SAS元数据服务器的用户。 该认证机制主要用于用户已经连接到元数据服务器的情况下,再连 接存储过程服务器、共享池工作区服务器和OLAP服务器。也可以将标 准工作区服务器(区别于共享池工作区服务器)配置为使用SAS令牌认 证。 4.LDAP集成认证 SAS有3种方式使用LDAP集成认证。 (1)主机使用LDAP SAS服务器主机使用LDAP提供方作为后端认证提供方,身份认证 将通过元数据服务器主机发送到LDAP提供方。从SAS服务器的角度来 看,该方法为主机认证。该方法要求SAS服务器主机能够识别LDAP提 供方。 (2)直接LDAP认证 通过适当的配置,使元数据服务器能够直接连接到其主机不能识别 的LDAP提供方,并将认证直接交给其完成,而不需要通过元数据服务 器主机将认证过程传递给LDAP提供方。 直接LDAP认证主要用于连接元数据服务器,以及从数据提供方到 OLAP服务器的直接连接,但是不支持工作区服务器和存储过程服务 器。 (3)sasauth使用LDAP(仅用于当SAS服务器部署于UNIX环境 时) 该方法用于认证从sasauth(UNIX主机认证模块)到LDAP提供方的 直接连接。 5.集成Windows认证 集成Windows认证(Integrated Windows Authentication,IWA)可使 SAS服务器接受已经成功认证到Windows系统的用户,是实现SAS系统 单点登录的一个解决方案,适用于Windows桌面客户端,以及Windows 和UNIX上的SAS服务器。可用于连接到元数据服务器和标准工作区服 务器,也可用于从数据提供方到OLAP服务器的直接连接。此外,结合 Web认证,集成Windows认证也能用于Web应用。IWA也是主机验证的 一种形式。 为了对在UNIX机器上的SAS服务器使用IWA,必须安装第三方产 品Quest Authentication Services。 6.通过信任方和信任用户连接 信任方(Trusted Peer)连接使元数据服务器接受使用私有协议的对 等SAS会话或SAS服务器的连接。该机制使SAS/CONNECT服务器能够 访问元数据服务器,使批处理进程可以连接到元数据服务器,而且在元 数据服务器集群中,该机制提供了对集群中节点之间的通信支持。 信任用户(Trusted User)连接指元数据服务器允许特权账号代表其 他用户,即信任已经被验证了的用户。该机制用于从Object Spawner、 OLAP服务器、SAS Web应用,以及报表批处理进程等连接到元数据服 务器。 详情请参考SAS Intelligence Platform:Security Administration Guide。 7.Web认证 元数据服务器接受已经认证到Web外围的用户。Web认证使SAS环 境能够利用Web领域的用户,而且还能实现Web领域的单点登录。 该认证机制应用于从SAS Web Application Server连接到元数据服务 器。 8.SAS认证API SAS 9.4使用Central Authentication Service(CAS)对访问SAS Web 应用程序的用户进行认证。SAS提供了一组基于REST软件架构的认证 API,它通过CAS对用户进行认证。开发人员可以通过调用这些API来进 行认证,也可以将自定义的应用程序与SAS Web应用程序相集成,使得 访问SAS Web应用程序时不必交互式地提供用户名和密码。 27.2.2 凭证管理 在SAS环境中,用户登录SAS智能分析平台及访问SAS服务器和第 三方数据服务器时,进行验证时使用的用户ID和密码,称为凭证。本节 主要介绍SAS如何进行凭证管理。学习和理解本节内容能帮助SAS管理 员根据用户对资源(主要是SAS服务器和数据服务器)的访问要求对用 户和组进行登录,以及对上述服务器的认证域等进行设置和管理。 1.登录 登录(Login)在此处是名词,指用户或组的外部账号信息。每个 用户在建立其SAS身份时必须提供登录。每个登录包括一个用户ID,但 不必包括密码。例如“SAS演示用户”的登录如图27.6所示。 图27.6 “SAS演示用户”的登录 用户可能有额外的登录以提供对其他系统的访问。例如,当用户需 要访问Oracle数据库时,则该用户可能包括额外的登录,如图27.7所 示。 注意,图27.7中两个登录的身份验证域是不同的。关于身份验证 域,下一小节会专门介绍。 图27.7 可访问Oracle的用户的登录 组常用于为属于该组的用户设置访问权限和权力,通常不需要有登 录信息。给组提供登录的主要目的是创建多个用户可用的共享账号。例 如提供对Oracles数据库的共享访问,可创建组“Oracle数据库用户”,并 为该组提供登录,如图27.8所示。属于该组的成员均可使用该登录访问 Oracle数据库。因为该账号用于访问第三方数据库,所以密码需保存在 该登录中。 图27.8 Oracle数据库组的登录 2.身份验证域 通常,每个用户的ID和密码都是成对的,并且只在特定范围内有 效。例如,数据库服务器和Web服务器分别有其各自的验证机制,所以 它们要求有不同的用户ID和密码。应用程序需要对众多不同资源进行访 问,这些资源可能要求不同的凭证。每次用户访问某个资源时,软件必 须使用该资源认可的凭证。 在SAS环境中,凭证是否有效是基于验证域(Authentication Domain)机制的。只有当用户登录账号的验证域与服务器的验证域相 匹配时,该用户才有可能访问该服务器。因此,必须正确地为每组使用 特定验证提供方的资源赋予验证域,然后将相同的验证域赋予给对该验 证提供方有效的凭证。这种机制在用户访问第三方DBMS或标准工作区 服务器时非常重要。 最简单的情况下,所有的登录和SAS服务器都是与同样的验证域 DefaultAuth相关联的。什么情况下可能需要使用多个验证域呢?如下: ·如果使用了Web验证,可能需要有用于Web领域用户ID登录的第二 个验证域。在配置Web验证的步骤中就包括了这个过程。 ·如果要无缝地访问第三方服务器,例如DBMS服务器,而第三方服 务器有其自己的用户,则需要创建用于该服务器及其登录的单独验证 域。在图27.7中,“SAS演示用户”可以无缝访问Oracle数据库中的数据。 ·如果标准工作区服务器不与元数据服务器共享认证提供方,或者 需要通过配置实现对标准工作区服务器的无缝个人化访问,则需要为标 准的工作区服务器及其登录设置单独的验证域。 验证域使用SAS Management Console的用户管理器或服务器管理器 创建。打开SAS Management Console,右键点击“用户管理器”或“服务器 管理器”,选择“身份验证域”。“身份验证域”对话框如图27.9所示。在该 对话框中,可创建、删除和编辑身份验证域。 图27.9 SAS环境的身份验证域 在为用户(或组)创建登录时,需要选择该登录的验证域,默认为 DefaultAuth。如图27.10所示为“SAS演示用户”添加Web验证域的登录的 界面,从“身份验证域”下拉列表选择“Web”即可为该登录指定验证域 为“Web”。也可以在该对话框创建整个SAS环境中可用的身份验证域 (通过“新建”按钮)。 图27.10 为登录选择身份验证域 3.凭证管理 SAS凭证(Credential)管理技术为每个连接的用户在内存中维护了 一个凭证列表,该列表被称为用户上下文。当用户通过连接配置文件初 始登录时,所提交的用户ID和密码作为用户上下文的第一条记录被插入 并缓存,此时,客户端会自动将第一条记录赋予DefaultAuth验证域。不 过,在以下几种情况下,则不会赋予DefaultAuth验证域: ·用户的连接配置文件中包括@saspw用户。 ·用户的连接配置文件的身份验证域字段为除了DefaultAuth之外的 其他验证域。 ·用户通过Web浏览器访问已经配置了Web验证的Web应用(或者 Web应用的Web配置因为某种原因指定了其他验证域)。 用户上下文第一条记录中的密码在以后访问服务器时可重用,尽管 该密码并未存储在元数据中。接着,客户端从该用户的元数据定义中获 取信息,并创建其他记录。 ·如果该用户或者用户所属组在元数据定义中有包括密码的登录 (因为这些登录用于对外连接,所以需要在元数据定义中包括密码), 那么这些凭证会添加到用户上下文中,该过程也称为凭证获取。如果用 户属于多个组,或者用户所属组是其他组的成员,那么这些组的登录信 息都会被添加到用户上下文中。但是直接组和间接组的登录记录与用户 的距离不同,间接组的登录记录离用户距离更远。 ·如果在会话过程中用户交互地提供了凭证,那么这些凭证也会添 加到该列表中。 当用户请求访问基于凭证认证的服务器时,客户端执行下列步骤: 1)检查目标服务器的元数据,确定该服务器所属的验证域。 2)检查用户上下文确定其是否包括目标服务器的验证域的凭证。 ·如果该上下文中包括目标验证域的缓存的记录,则使用该记录。 ·如果用户上下文中包含目标验证域从元数据中获取的记录,则使 用该记录。如果有多个获取的记录,则使用离用户最近的记录。 ·如果所获取的记录离用户同等距离(例如,用户是两个组的直接 成员,而且这两个组都拥有相关验证域的登录),则每次连接对应验证 域的目标服务器时,会使用相同的登录信息,但是不能控制使用哪一 个。 ·如果用户上下文中不包含目标验证域的记录(比如是桌面客户 端,则会提示用户输入凭证,但对Web应用不会),那么所输入并通过 了认证的凭证会加入用户上下文中供下次使用。 3)将凭证发送到目标服务器进行认证。 4.凭证管理示例 下面以“SAS开发用户”登录SAS Enterprise Guide(登录其他SAS桌 面应用程序过程也类似)执行任务为例,讲解SAS进行凭证管理的过 程。“SAS开发用户”的登录及所属组和角色信息分别如图27.11和图 27.12所示。 图27.11 “SAS开发用户”的登录 图27.12 “SAS开发用户”为“Oracle数据库用户”组成员 “SAS开发用户”包括两条登录:主机认证的登录和Web认证的登 录。同时,该用户还是“Oracle数据库用户”组的成员。“Oracle数据库用 户”的信息请参考图27.8。 当“SAS开发用户”(用户ID为sasdev)登录SAS应用程序SAS Enterprise Guide时,执行下列步骤: 1)用户通过连接配置信息提供用户名和密码。这时sasdev的用户上 下文有第一条记录,如表27.1所示。注意,每个条目都包括验证域,用 于将凭证与对服务器有效的凭证进行配对。 表27.1 用户上下文包含一条记录 2)SAS读取“SAS开发用户”的元数据信息,并将该用户的登录(图 27.11中显示的登录)添加到用户上下文中。该例中“SAS开发用户”所包 括的登录信息中均不包含密码,所以不会添加到用户上下文中。 3)SAS读取“SAS开发用户”所属组“Oracle数据库用户”(如图27.8 所示)的登录信息,并添加到用户上下文中,如表27.2所示。 表27.2 用户上下文包含两条记录 4)当用户提交作业到SAS工作区服务器时,SAS检查工作区服务器 连接的身份验证域,如图27.13所示。其中的身份验证域为 DefaultAuth。 图27.13 工作区服务器的身份验证域 5)SAS从用户上下文中查找身份验证域为DefaultAuth的凭证。找 到第一条记录,使用其用户名和密码进行验证。 6)当该用户通过SAS Enterprise Guide访问Oracle数据库中的表时, SAS检查“Oracle数据库服务器”连接的身份验证域,如图27.14所示,身 份验证域为OraAuth。 图27.14 “Oracle数据库服务器”的身份验证域 7)SAS从用户上下文中查找身份验证域为OraAuth的凭证(即用户 名oraluser及存储的密码)。 8)当用户视图通过“Platform Process Manager预定服务器”预定作业 流时,SAS检查该预定服务器的身份验证域,如图27.15所示。身份验证 域为LSFAuth。 图27.15 Platform Process Manager预定服务器的身份验证域 9)SAS查找“SAS开发用户”的用户上下文,未找到包含该身份验证 域的凭证,这时SAS会弹出对话框要求输入用户名和密码。 10)当用户在对话框中提供用户名和密码,并成功通过认证后,该 凭证会加入“SAS开发用户”的用户上下文中,如表27.3所示。 表27.3 用户上下文包含3条记录 当用户再次访问身份验证域为LSFAuth的服务器时,就可以使用该 凭证进行认证了。 27.2.3 认证到元数据服务器 每个用户必须有访问元数据服务器的账号,才可以直接访问或通过 信任关系访问元数据服务器。最简单的情况是,用户已经拥有元数据服 务器的主机已知账号,则使用默认的主机认证,此时不需要额外的配 置。例如,元数据服务器在UNIX上,用户有该UNIX主机认识的LDAP 提供方的账号,或者元数据服务器在Windows上,用户有Windows主机 所在AD域账号。 在某些情况下,用户有元数据服务器的主机不认识的账号,考虑如 下情景: 情景1:元数据服务器在UNIX上,用户有Active Directory账号。 可通过PAM(Pluggable Authentication Modules)机制使UNIX主机 认识该账号。 情境2:用户有元数据服务器不认识的LDAP提供方的账号。 可配置“直接LDAP认证”使元数据服务器认识该LDAP提供方。 情境3:用户有元数据不认识的Web外围账号。 可配置“Web认证”使元数据服务器信任Web外围账号。需要注意的 是,Web认证只能解决用户登录Web应用的问题,在登录SAS桌面应用 程序时仍需要提供账号给元数据服务器或其主机认证。 27.2.4 认证到计算服务器 已经连接到元数据服务器的用户可以通过SAS令牌(Token)认证 访问OLAP服务器、存储过程服务器,以及共享池工作区服务器。 在标准配置中,OLAP服务器支持IWA直接连接,类似于元数据服 务器的支持IWA从SAS桌面客户端进行直接连接。但是,在SAS平台 中,大多数对OLAP服务器的访问是客户端首先连接到元数据服务器, 然后再连接到OLAP服务器,而不是直接连接。在这种情况下,连接到 OLAP服务器使用SAS令牌认证,不使用IWA。 为了无缝访问工作区服务器,必须与元数据服务器协调工作区服务 器。在初始配置时,工作区服务器使用主机认证,可以将其配置为使用 SAS令牌或使用IWA认证。 27.2.5 认证到数据服务器 为了提供对第三方数据服务器的访问,需要通过用户或组的账号选 项卡在元数据中保存用户的ID和密码,具体请参考“凭证管理”。可选择 对单个用户进行设置,或者设置组使用共享账号,也可以采用两者组合 的方式。 27.2.6 单点登录 单点登录(Single-Sign On)是多个相关的独立软件系统所具有的访 问控制属性。使用该属性,用户登录一次就可获得对所有系统的访问, 而不必再被重复提示登录。表27.4给出了能够提供单点登录属性的方 法,以及每种方法提供单点登录的应用程序和服务器对照表。 表27.4 SAS应用程序和服务器及支持单点登录的机制对照表 ①凭证重用和获取请参考27.2.2节“凭证管理”。 ②这里的SAS服务器并不是指所有的SAS服务器。对于每种认证机 制可提供单点登录的SAS服务器,请参考27.2.1节“认证机制”给出的适 用服务器范围。 此外补充以下两点: ·SAS认证API提供了将用户开发的应用程序与SAS Web应用程序集 成的方法,可从已经验证过的自定义应用程序访问SAS Web应用程序。 ·SAS Web应用程还支持与第三方安全管理产品集成,例如IBM WebSeal、CA SiteMinder已提供企业整个系统的单点登录。 27.3 授权 身份验证成功后,系统必须确认用户已被授权访问特定的资源,同 时要明确被许可对该资源执行哪些操作。为用户授予特定资源的访问权 限的过程称为授权(Authorization)。SAS智能分析平台提供了基于元 数据的授权层,来补充主机环境或其他系统对资源的保护。 27.3.1 元数据授权 元数据授权层用于管理对几乎所有的元数据对象的访问,例如报 表、表定义、信息映射、作业、存储过程以及服务器定义等。注意,有 些客户端,例如SAS Enterprise Guide、Data Integration Studio,允许用 户运行SAS程序越过元数据层直接访问数据。对于这种情况,可通过使 用绑定于元数据的元数据边界逻辑库(metadata-bound library)来避 免,详细情况请参考SAS Intelligence Platform:Security Administration Guide的相关章节。 1.访问控制项 SAS为SAS数据对象提供了多种访问控制项。如表27.5所示为通用 的权限访问控制及其描述。 表27.5 访问控制项 此外,对于很多SAS对象,如果涉及SAS对象后端的数据和服务器 管理,SAS还提供了特殊目的的权限控制,如表27.6所示。 表27.6 特殊目的的访问控制项 有时为了执行一个任务,用户必须在所有层有充分的访问权限,这 时应该怎么办?具体会在后面27.3.4节“访问SAS对象”中详细介绍。 2.权限设置粒度 SAS提供通过以下粒度来进行权限设置。 (1)元数据存储库级的访问控制 存储库级控制是从存储库ACT(Access Control Template,访问控制 模板)的权限模式进行管理的。所有注册用户都应该在基础存储库ACT 的权限模式中具有ReadMetadata和WriteMetadata权限。 (2)SAS对象级访问控制 对象级控制管理对特定对象的访问,如管理对报表、信息映射、存 储过程、表、列、立方体或文件夹的访问。可以分别(如显式设置)定 义资源层控制,或根据模式(通过应用访问控制模板)来定义资源层控 制。 (3)细粒度的控制 细粒度的控制指通过添加约束(称为权限条件)来显式地给资源内 的数据子集(例如表的行、列或立方体的维度)赋予读权限。 3.权限关系 SAS提供如下权限关系网络。 (1)对象继承 在对象继承网络中,设置在一个对象上的权限可能会影响许多其他 对象,最简单的这类情况是SAS文件夹树。大多数SAS对象在元数据存 储库中以文件夹形式组织,设置在文件夹上的权限会影响文件夹中对象 的权限。在图27.16中,表HMEQ会从其所在的文件夹Shared Data继承权 限。 SAS对象继承的权限顺序如下: 1)每个对象首先会使用最基础的存储库层控制(Default ACT)所 定义的访问控制。Default ACT定义的默认权限可通过如下方式查看, 在SAS Management Console中,选择“授权管理器”→“访问控制模 板”→Default ACT,右键单击“属性”,在“Default ACT属性”对话框中, 选择“权限模式”选项卡。如图27.17所示为Default ACT对“SAS系统服 务”组进行权限模式设置。 图27.16 HMEQ从Shared Data继承权限 图27.17 Default ACT 2)优先级较高的是要访问的对象的父对象(例如报表所处的SAS 文件夹)对于用户或组的访问权限,即对象会继承其父对象的访问控 制。如图27.18所示为表HMEQ的授权信息,图中标识为灰色的权限项表 示该权继承自其父对象。 图27.18 继承自其父对象的权限 3)显式地通过ACT作用于对象的授权则具有更高的优先级,即显 式地应用ACT,其优先级更高。使用访问控制模板时先通过授权管理器 创建模板,然后选择要设置的对象,右键单击“属性”,在“授权”选项卡 中,使用按钮“访问控制模板”来选择需要应用的模板。图27.18中标识为 绿色的权限项表示该权限来自于访问控制模板。 4)显式地直接赋予对象的授权具有最高的优先级。在“授权”选项 卡上面的窗口中,可以添加用户或组。在进行直接授权时,先选择“用 户和组”,然后通过勾选“授予”框或“拒绝”框来授予或拒绝对象相应的 权限。图27.18中无背景颜色的权限项表示该权限通过直接授权获得。 (2)身份成员关系 在身份成员关系的网络中,给一个身份分配的权限可能影响许多其 他身份。例如,如果给一个组赋予了访问报表的权限,那么该访问权限 也会应用到该组的成员上。这种成员关系网络通过优先顺序管理,优先 级最高的是直接赋给该用户的权限,其次是用户所属直接组的权限,再 次是用户所属非直接组权限,最后是用户所属隐式组SASUSERS或 PUBLIC的访问控制权限,如图27.19所示。 图27.19 27.3.2 身份成员关系的权限 访问元数据文件夹 根据SAS权限继承关系机制,可以通过对SAS文件夹进行权限设 置,来统一管理其子文件夹,以及其中所有SAS对象的权限。 对文件夹设置权限的一个方式是创建几个常用ACT(包括常用组的 权限,例如PUBLIC、SAS管理员、SAS系统服务等),并且将其应用 到要保护的文件夹上。如果还需要对特定的组或用户进行授权,则可以 通过继续对目标文件夹添加显式的控制来对ACT设置进行补充。 27.3.3 访问数据 SAS元数据授权层可用来限制对SAS数据的访问,但仅应用于当用 户在元数据可知的上下文中请求数据时。SAS表(数据集)存储为主机 操作系统上的文件。通常,在主机操作系统上有访问和读取权限的用户 都可以访问和读取SAS表。 例如,假定用户在元数据中注册了SAS逻辑库和表,在SAS Management Console中,设置用户A对表Salary没有读取元数据权限(即 拒绝该权限)。那么在元数据可知的应用程序中,用户A看不到表 Salary。但是,如果用户A有访问该表的物理文件的主机访问权限,那 么用户A可以在Base SAS中打开(或在Base SAS中使用LIBNAME语句 打开)该文件并查看所有的数据。也就是说,在元数据层拒绝读取元数 据权限不会应用在直接访问该表时。因此,需要考虑并解决对SAS逻辑 库和表的物理层访问的问题。 下面几种方式可用于保护SAS数据: ·仅提供中介访问。在主机层,不提供用户的主机身份对数据的物 理访问,而仅提供称为启动凭证(Launch Credential)的服务账号的物 理访问权限。当用户需要访问数据时,可通过工作区服务器(需配置为 SAS令牌认证)、共享池工作区服务器,或者存储过程服务器来实现, 这些服务器进程会使用该服务账号访问主机物理数据。这时,用户通过 这些服务器实现的访问受元数据层授权机制的保护。 ·将数据绑定到元数据上,这样所有SAS对数据的请求都受元数据层 权限的保护。详细情况请参考SAS支持网站的文档SAS Guide to Metadata-Bound Library。 ·还可以通过对静态数据进行加密,以达到保护数据的目的。 27.3.4 访问SAS对象 本节给出的一系列表格,展示了对于给定的SAS对象要执行各种任 务时用户必须具有的权限。 1.访问SAS文件夹 在访问SAS文件夹时,用户必须对SAS元数据存储库(表格中简称 为存储库)、文件夹所在的父文件夹、文件夹自身,以及项(如果是对 文件夹中的项进行操作)有相应的访问权限。各层的访问权限如表27.7 所示。 表27.7 访问SAS文件夹要求的权限 ①如果父文件夹为根文件夹,则需要对根文件夹有RM、WM访问 权限。 2.访问数据表 在访问SAS表时,用户需要对SAS元数据存储库、SAS服务器(如 果使用了SAS服务器)、表所属的逻辑库、表所在的父文件夹、表自身 的元数据,以及表的列有相应的访问权限。各层的访问权限如表27.8所 示。 表27.8 3.访问报表 访问数据表需要的权限项 在访问SAS报表时,用户需要对SAS元数据存储库、报表元数据所 在的父文件夹、报表自身的元数据、报表生成使用的存储过程和信息映 射(如果使用了),以及生成该报表所使用的数据(通过元数据 LIBNAME引擎或OLAP服务访问的数据)有如表27.9所示的访问权限。 表27.9 访问报表需要的权限项 4.访问信息映射 在访问SAS信息映射时,用户需要对SAS元数据存储库、信息映射 元数据所在的父文件夹、信息映射自身的元数据、信息映射使用的存储 过程(如果使用了),以及该信息映射所使用的数据(通过元数据 LIBNAME引擎或OLAP服务器访问的数据)有如表27.10所示的访问权 限。 表27.10 访问信息映射需要的权限项 ①如果信息映射中使用到存储过程,则要求对该存储过程有给出的 权限。 ②对通过元数据LIBNAME引擎或OLAP服务器访问的数据要求有 Read权限。 5.访问存储过程 在访问SAS存储过程时,用户需要对SAS元数据存储库、存储过程 元数据所在的父文件夹、存储过程运行的应用服务器(可能是存储过程 服务器或工作区服务器)、存储过程自身的元数据,以及存储过程访问 的数据(通过元数据LIBNAME引擎或OLAP服务访问的数据)有如表 27.11所示的访问权限。 表27.11 访问存储过程需要的权限项 ①对通过元数据LIBNAME引擎或OLAP服务器访问的数据要求有 Read权限。 6.访问OLAP立方体 在访问SAS OLAP立方体时,用户需要对SAS元数据存储库、执行 访问行为的SAS应用服务器、立方体使用的模式(Schema)、立方体元 数据所在的父文件夹、立方体自身的元数据,以及构建立方体的源数据 (通过元数据LIBNAME引擎进行访问的数据)有如表27.12所示的访问 权限。 表27.12 访问OLAP立方体需要的权限项 ①对通过元数据LIBNAME引擎访问的数据要求有Read权限。 7.访问OLAP共享维度 在访问SAS OLAP共享维度时,用户需要对SAS元数据存储库、执 行访问的SAS应用服务器、共享维度使用的模式(Schema)、共享维度 元数据所在的父文件夹、使用共享维度的立方体、共享维度自身的元数 据,以及共享维度的源数据(通过元数据LIBNAME引擎进行访问的数 据)有如表27.13所示的访问权限。 表27.13 访问OLAP共享维度需要的权限项 ①对通过元数据LIBNAME引擎访问的数据要求有Read权限。 ②不能删除与任何立方体关联的共享维度。 ③对通过元数据LIBNAME引擎访问的数据要求有Read权限。 8.访问发布频道 在访问SAS发布频道(Publishing Channel)时,用户需要对SAS元 数据存储库、发布频道元数据所在的父文件夹、频道自身的元数据,以 及订阅者(通过元数据LIBNAME引擎进行访问的数据)有如表27.14所 示的访问权限。 表27.14 访问发布频道需要的权限项 ①如果频道拥有存档持久化存储,那么需要WM权限。 ②内容仅仅会发布给拥有RM权限的订阅者。 27.3.5 数据的细粒度控制 有时因为数据敏感性,经常需要设置不同的用户仅能看到数据的不 同部分,如某列或某行。例如,每个销售人员应该只能访问自己的奖 金。在有些情况下,这种要求可能是为了防止信息过载。例如,全国性 组织内的区域销售团队可能只对其所在区域的销售信息感兴趣。细粒度 访问通常是基于组织结构进行区分的,例如管理层级或产品矩阵中的用 户所在的位置。数据的可见性可能依赖于简单的、站点特定的条件,例 如用户的安全检查级别,或者依赖于包括多种考虑的复杂条件。 细粒度的控制可用来指定谁可以访问表的特定列或立方体维度内的 特定维度成员。这些控制通常是基于用户特征(例如员工ID或组织单 元)来提取数据的子集的。例如,包含病人医疗信息的表可能会由行级 (row-level)权限进行保护,该行级权限使医生只能看到自己病人的数 据。 细粒度的控制是基于过滤器的,并且依赖于规范化及与这些过滤器 一起使用的目标数据。当使用细粒度控制时,对于用户请求浏览数据, 有以下3种可能的授权决策结果。 ·授予:请求的用户可以访问所有数据。 ·拒绝:请求的用户不能访问任何数据。 ·条件授予:请求的用户只能访问符合指定的过滤条件的数据。 下面的组件提供了细粒度控制的实现。 (1)BI行级权限 BI行级权限为SAS数据集和第三方关系型数据访问提供了通过信息 映射进行的过滤。在SAS Information Map Studio中可使用INFOMAPS过 程定义和使用这些过滤器。请参考SAS Guide to BI Row-Level Permissions获得详细信息。 BI行级权限主要是与SAS Web Report Studio一起使用。在SAS Visual Analytics中可用的行级安全特性的信息机制与此不同,请参考 SAS Visual Analytics:Administration Guide。 (2)SAS OLAP Server SAS OLAP Server使用了MDX表达式对SAS OLAP数据进行过滤。 可以在SAS Management Console、SAS OLAP Cube Studio或SAS Data Integration Studio中定义和使用这些过滤器。请参考SAS OLAP Server: User’s Guide获取详细信息。 (3)SAS Scalable Performance Data Server SAS Scalable Performance Data Server使用户定义基于连接客户端的 用户ID过滤行的数据库视图。该功能由@SPDSUSR系统变量提供。请 参考SAS Scalable Performance Data Server:Administrator’s Guide获取详 细信息。 27.4 加密 保证数据的保密性,使其足够安全,一直是企业级用户的关键需求 之一。对于网络上的业务交易来说,保密性非常重要,例如在企业与其 消费者之间、企业之间,以及企业内部的业务交易都会涉及保密数据, 这种保护数据的过程称为加密。加密(Encryption)是通过数学过程将 可以理解的数据(明文)转换成不可理解的格式(密文)。当应用适当 的密钥解密(解锁)密文时,即可翻译成明文。SAS提供两类加密强 度: ·SASProprietary。SASProprietary提供了SAS私有算法,它使用32位 固定编码,仅能保护信息不被意外泄露。 ·SAS/SECURE软件。SAS/SECURE提供RC2、RC4、DES、AES等 工业标准加密算法。由于进口保护,SAS/SECURE在中国不能使用,本 书也不对SAS/SECURE进行过多讨论,有兴趣的读者请参考SAS Intelligence Platform:Security Administration Guide进行学习。 SASProprietary提供中等程度的安全性,足够保护数据不被随意查 看。SAS/SECURE和TLS(Transport Layer Security,传输层安全性)提 供较高级别的安全性。 加密用于帮助保护传输中(in transit)的信息和静态(at rest)信 息。 ·线上(over-the-wire)加密保护在传输中的数据。从SAS服务器发 出或发送到SAS服务器的传输中密码都是加密或编码的。 ·磁盘上(on-disk)加密保护静态数据。静态数据包括配置文件中 的密码、登录密码和内部账号密码等,这些都经过了加密或编码。当指 定数据集选项ENCRYPT=时,SAS数据集也会被加密。 27.4.1 加密提供方 SAS中可使用的加密提供方有SASProprietary、TSL/SSL和SSH,以 及通过SAS系统选项进行加密。 1.SASProprietary SAS私有算法是在Base SAS软件中包括的固定编码算法。登录对象 的密码就是使用SAS私有算法存储的。磁盘上配置文件中的密码默认使 用SAS私有算法SAS002编码。内部账号密码使用MD5哈希算法加密后 存储在元数据存储库中。 2.TLS/SSL TLS是SSL(Secure Socket Layer,安全套接层)V3.0的后续版本。 TLS和SSL是提供网络数据隐私、数据完整性以及认证的协议。TLS使 用的加密算法包括RC2、RC4、DES、TripleDES、AES等。除了提供加 密服务以外,TLS还会执行客户端和服务器认证,并使用消息认证码保 证数据的完整性。所有主流浏览器都支持TLS。许多网页使用该协议来 保护需要保密的用户信息,例如信用卡号。TLS协议是独立的应用,其 对其上层的HTTP、FTP和Telnet透明。 Base SAS软件兼容SSL2.0、SSL3.0和TLS1.0。可在SAS客户端和 SAS服务器之间配置TLS/SSL来加强传输安全性。具体配置步骤请参考 SAS相关文档。 3.SSH SSH(Secure Shell)协议使用户能够通过安全连接访问远程计算 机。尽管SAS软件不直接支持SSH功能,但是在SAS客户端和SAS服务 器之间的数据流动可以使用SSH的隧道特性。利用SSH的端口转发 (Port Forwarding)方法,SSH客户端和SSH服务器可作为SAS客户端和 SAS服务器之间的代理,这样就可使用隧道通过SAS客户端端口向SAS 服务器端口发送信息了。 27.4.2 加密ODS PDF文件 SAS ODS(Output Delivery System,输出交付系统)可用于产生 PDF输出。当PDF文件不使用密码保护时,任何用户都可查看和编辑 PDF文件。但是,通过指定ODFSECURITY系统选项对PDF的输出文件 进行密码保护后,所生成的PDF文件在打开时就会要求提供密码。 限制和允许用户访问、组合、复制、修改ODS PDF文件的系统选项 包括PDFACCESS|NOPDFACCESS、 PDFASSEMBLY|NOPDFASSEMBLY、 PDFCOMMENT|NOPDFCOMMENT、 PDFCONTENT|NOPDFCONTENT、PDFCOPY|NOPDFCOPY、 PDFFILELIN|NOPDFFILEIN、PDFPASSWORD、PDFPRINT、 PDFSECURITY。各系统选项详细信息请参考SAS文档SAS System Options:Reference。 27.4.3 SAS加密系统选项 如果客户端计算机上的SAS会话与SAS服务器之间交换数据,可指 定该SAS会话执行期间对这些数据实施加密的SAS系统选项。例如,如 果SAS/CONNECT客户端连接到服务器上的Connect Spawner,可在 Connect Spawner启动命令中指定加密系统选项。 SAS提供的加密系统选项,请参考SAS文档Encryption in SAS及SAS System Options:Reference进行学习。 27.4.4 PWENCODE过程 PWENCODE过程使用户能够对密码进行编码。当SAS程序访问关 系型数据库管理系统和各种SAS服务器(例如SAS/CONNECT服务器、 SAS/SHARE服务器、SAS IOM(Integrated Object Model,集成对象模 型)服务器(SAS元数据服务器)等)时,在SAS程序中可使用所编码 的密码替换明文密码。 PWENCODE过程的基本形式如下: PROC PWENCODE IN= '密码' <OUT=文件引用> <METHOD=编码方法>; RUN; 其中: ·IN=指定密码明文。 ·OUT=指定所编码的密码保存的外部文件的文件引用。该选项为可 选,不指定时,所编码的密码默认输出到SAS日志中。 ·METHOD=指定编码方法。该选项为可选,不指定时,默认使用 sas002编码。当Base SAS软件中包含SAS/SECURE时,可将其设置为 sas003或sas004。 下面为对密码进行编码的示例代码: proc pwencode in='mypasswd'; run; 所编码的密码输出在SAS日志中,如图27.20所示。 图27.20 PWENCODE过程日志 日志中显示编码后的密码为 {SAS002}F58A36490A1DF8B202F2139254738F7E。 下面为在SAS程序中使用编码的密码的示例: libname sqllib odbc dsn=SQLServer user=testuser password=""{SAS002}F58A36490A1DF8B202F2139254738F7E"; 27.5 安全性审计 系统的审计功能可确保用户行为的可问责性,并且可验证安全策略 是否已经被应用,同时,还能够将审计信息用作调查工具。通常,安全 性系统通过记录用户、系统和应用程序的活动来跟踪可问责性。SAS提 供了安全性报告宏、SAS日志模块,并使用LOG4J等审计功能和机制来 完成这些记录。 此外,通过检查性能信息或某些类型的错误和条件,审计跟踪还可 用于验证系统的健康状况。SAS审计性能测量包以报表的形式展示SAS 环境的审计性能相关的信息及SAS环境运行状态。 27.5.1 SAS安全性报告宏 安全性报告用于获取SAS环境的安全性配置,并确定用户如何访问 被配置的受安全性保护的资源。这种报告帮助保证既定的安全性计划在 发挥作用,并且在如预期地保护资源,还能帮助追溯可能存在问题的访 问。 用作安全性报告的SAS宏可用于连接到元数据服务器,并使用SAS 元数据服务器的安全性管理API来抽取授权信息。安全性报告用于创建 元数据层访问控制设置的快照,所产生的安全信息可用于产生关于当前 安全性设置的报表,或者作为跨时间比较设置的数据点。 安全性报告的第一个任务是抽取、过滤并格式化指定的身份 (identity)、权限和对象的授权数据。SAS提供了宏来帮助执行该任 务。下面的示例代码使用主安全报告宏%MDSECDS为指定的文件夹及 其内容生成一组授权数据集,并放在临时逻辑库中。授权数据集包括 work.mdsecds_join、work.mdsecds_objs、work.mdsecds_pconds、 work.mdsecds_permsl、work.mdsecds_permsw。 /* connect to the metadata server */ options metaserver=metahost.corp.com metauser="sasdemo" metapass="MyPassword"; /* run the main report macro against a target folder */ %mdsecds(folder="\Shared Data"); run; 安全性报告中的第二个任务是基于这些授权数据集创建报表。例 如,下面的示例代码打印了前面示例生成的授权数据集组中的主表 (work.mdsecds_join)的部分数据。 proc print data=work.mdsecds_join noobs; var objname publictype identityname run; WriteMetadata; 上述代码的部分输出如图27.21所示。 图27.21 mdsecds_join的部分数据 因为主安全报告宏%MDSECDS的输出是SAS数据集,所以可以通 过使用SAS报表技术来创建更为高级的报表,例如使用授权数据集作为 数据源创建信息映射,接着使用SAS Web Report Studio来创建基于该信 息映射的报表,或编写SAS ODS代码基于授权数据集来构建HTML报表 等。 关于SAS安全性报告宏更为详细的信息,请参考SAS文档SAS Intelligence Platform:Security Administration Guide。 27.5.2 SAS日志模块 SAS日志模块(facility)用于收集、分类和过滤SAS服务器环境和 SAS编程环境中的日志消息,并且它会将日志消息写入各种输出设备的 框架。该日志模块支持问题诊断与解决、性能和容量管理,以及审计和 合规。 SAS日志模块基于预定的消息类别记录消息,例如Admin类别表示 管理性消息,App类别表示应用程序消息,Perf类别表示性能消息。每 种类别的消息能够同时写入文件、控制台和其他位置。日志模块可根据 下面的阈值过滤消息:TRACE、DEBUG、INFO、WARN、ERROR和 FATAL。日志模块由大多数SAS服务器进程使用,在SAS程序中也可以 使用。 性能相关的日志事件可以被ARM(Application Response Measurement)4.0处理。 注意 SAS日志模块是与SAS日志不同的SAS内部日志系统。传 统的SAS日志只显示SAS程序和全局语句执行结果的信息、警告和错 误。 1.SAS服务器的日志 SAS智能分析平台使用标准的SAS日志模块来执行SAS服务器的日 志记录。每种服务器都具有一个日志配置文件,可控制该服务器的日志 文件位置、内容以及格式。SAS Deployment Wizard提供了SAS服务器的 默认日志配置文件,需要时可修改这些文件来调整日志配置,也可以通 过SAS Management Console的服务器管理来动态调整日志记录级别。 2.SAS程序中使用SAS日志模块 SAS语言使用户能够在DATA步(函数或DATA步组件对象)和宏 程序中使用SAS日志模块。如果在SAS程序中需要记录任何诊断级别的 消息,那么其中必须包括日志事件(Event)。这些诊断级别从低到高 为TRACE、TRACE、DEBUG、INFO、WARN、ERROR和FATAL。 SAS日志模块主要包括如下组件。 ·日志模块函数:在DATA步中可使用3个日志模块函数, LOG4SAS_APPENDER、LOG4SAS_LOGGER和 LOG4SAS_LOGENVENT,分别用于创建Appender、Logger和记录日志 消息。 ·组件对象:SAS提供了两个预定义的组件对象(Logger对象和 Appender对象),可用于在DATA步中访问SAS日志模块。这两个组件 对象接口使用户能够记录日志事件,并将这些事件写入合适的位置。 ·日志自动调用宏:如果需要使用日志自动调用宏,就必须初始化 SAS程序的日志模块。为了在SAS中自动调用宏,必须设置系统选项 MAUTOSOURCE,并在SAS处理任何其他日志模块自动调用宏之前, 需要先调用宏%LOG4SAS。当SAS启动时设置该选项,其后将不再要求 其他操作,除非该选项被关掉。 27.5.3 Web应用程序的日志 SAS Web应用程序使用LOG4J(Apache的一个开放源代码项目)来 记录日志。每个Web应用程序开始运行时,SAS从sas-configdir\Lev1\Web\Common\LogConfig读取该应用程序的LOG4J配置文件。 在读取LOG4J配置后,允许动态日志更改的应用程序检查在SAS Web Administration Console设置的修改。 27.5.4 SAS审计性能测量包 审计材料和日志文件包含了大量信息,更重要的是,它以一种有益 且易于理解的方式来解释和呈现这些信息。SAS APM(Audit, Performance and Measurement,审计、性能和测量)包是由SAS提供的 为企业配置的供客户下载、安装,并以易于理解的报表形式展示SAS环 境信息的工具集。SAS APM包还允许SAS Enterprise BI站点监视SAS 9BI架构的状态,实施合规审计认证报告,以及报告SAS 9BI分析服务器 环境的性能和可使用性等。 SAS APM包的特征和功能是通过一组SAS语言程序、逻辑库和操作 系统特定的脚本实现的,它主要提供了以下3类报表特性: ·元数据服务器审计报告,包括管理性的授权和认证修改、管理员 组和用户ID访问控制修改、用户ID认证和授权模式,以及用户认证和密 码失败尝试等。图27.22和图27.23分别为部署了SAS APM的SAS环境的 审计及性能报告汇总示例,其中展示了元数据服务器审计中的访问控制 变更报告。 图27.22 图27.23 审计及性能报告汇总 访问控制变更报告 ·SAS分析服务器使用报告,包括元数据逻辑库报告、SAS过程和 DATA步报告、SAS OLAP服务器立方体使用情况、SAS存储过程服务 器使用情况和用户报告,以及SAS分析服务器处理器使用情况报告等。 图27.24和图27.25分别给出了一段时间内访问SAS工作区服务器排名前 十的用户情况报告(部分),以及工作区服务器执行的DATA步和SAS 过程的使用情况报告示例。 图27.24 访问工作区服务器前十用户 图27.25 工作区服务器执行的DATA步和SAS过程使用情况报告 ·SAS环境状态报告,包括Web应用程序服务器和服务可用性、分析 服务器层可用性、分析服务器响应时间报告、失败状态条件的实时报警 特征等。图27.26给出了SAS环境中SAS应用服务器及Web应用程序状态 报告。 图27.26 SAS环境状态报告 SAS APM包还允许站点管理员创建和部署框架来定制报表扩展器 功能,从而满足实时状态报告的操作业务需求。此外,信息技术 (Information Technology,IT)和业务单元组织可以使用这些报表去跟 踪分析服务的使用情况和环境用户的使用情况,同时,还可以分析SAS 过程、SAS存储过程以及SAS数据逻辑库的访问。 27.6 本章小结 本章简要介绍了SAS智能平台安全性相关的主要特征,包括身份标 识、身份验证、授权、加密与审计功能。 ·身份标识。SAS提供了用户和组管理登录凭证,并且提供了角色来 控制对应用程序功能的访问。 ·验证。SAS支持多种验证机制,包括主机认证、令牌认证、LDAP 集成认证、Web认证、IWA等,以及多种单点登录机制。SAS还可以与 第三方安全产品集成,以满足不同的身份验证需要。 ·授权。SAS主要基于元数据授权层来控制对SAS对象的访问,以及 基于过滤器来对数据进行细粒度访问。 ·加密。SAS提供了对传输中数据和静态数据的加密方法。 ·安全性审计。SAS提供了安全性报告宏、SAS日志模块、LOG4J及 APM包等审计功能和机制。 安全管理是SAS智能平台管理的一个重要组成部分,仔细阅读本章 的内容即可以快速全面地理解SAS智能平台的必要知识,有助于深入学 习更加详细的资料。 第28章 SAS智能平台的高可用性 高可用性是当系统软件或硬件发生故障时,保持系统和应用程序仍 然可以操作和访问的机制。高可用性(High-availability,HA)与灾难 恢复(Disaster Discovery)的区别在于:高可用性通常集中在通过提高 系统抗故障能力来最小化停机时间;灾难恢复是指在系统发生故障、停 机或灾难发生后恢复系统的方法。 在大型的商业系统设计中,在考虑高可用性和安全性时,往往会分 为两个阶段进行考虑。第一个阶段是回答和解决可行性的问题,也就是 说,首先要明确所要求的设计目标在技术上或成本上是否是可以实现 的。第二个阶段才是如何去实施,也就是说,如果是可行的,那么接下 来才是如何去实现。本章主要是面向第一阶段,介绍高可用性的相关概 念,理解SAS可以实现什么样的高可用性设计,应用的技术是什么,这 些知识对合理设计一个大规模SAS应用是至关重要的。对于实现这些设 计的详细方法和步骤,建议联系当地SAS公司的咨询顾问获取相应的技 术支持和详细的技术资料。 28.1 高可用性相关概念 高可用性的相关概念包括主动/被动设置、主动/主动设置、故障转 移、负载均衡等。在实施高可用技术时,通常还需要使用共享存储,这 里也会对其一并进行介绍。 1.主动/被动设置 主动/被动设置(Active/Passive Setup)是一种包括多个服务器的集 群配置,在该集群中,在故障转移发生前,由主(primary)节点处理用 户请求,在故障转移发生后,由备用(stand-by)节点接管用户请求。 2.主动/主动设置 主动/主动设置(Active/Active Setup)也是一种包括多个服务器的 集群配置。在该集群中,所有的服务器同时处理用户请求。当集群中的 节点发生软件或硬件故障时,分配到故障节点的处理任务和流量将会被 转移给现有的节点或在剩余的节点间进行负载均衡。这通常要求各个节 点使用同构的软件配置。 主动/主动方法通常可以提供比主动/被动方法更高级别的可用性和 更少的停机时间。然而,这种可用性的增加由于需要专门的软件或硬件 设备,以及额外的产品许可,通常使得成本较高。 3.故障转移 集群中的主服务器节点发生故障时,响应客户端请求的应用程序或 服务从主服务器切换到备用服务器的过程称为故障转移(Failover)。 这种切换通常是由集群软件或高可用性软件自动执行的。 在故障转移集群中,某一时刻只有一个服务器上的应用程序或服务 处于活动状态。在主服务器发生故障前,由应用程序或服务运行在主服 务器上提供服务。在主服务故障发生时,该应用程序或服务在另一个服 务器重新启动,以响应后续请求。 在故障转移过程发生后,还有一个可能发生的过程叫故障恢复 (Failback)。故障恢复是系统从故障转移后的状态恢复到初始状态 (故障前)的过程。在故障转移完成之后,如果导致主服务器失败的根 源被修复了,应用程序或服务及其相关资源将切换回主服务器。该过程 通常由相应的软件或硬件与故障转移一起提供并自动完成。 4.负载均衡 负载均衡(Load-balancing)是将工作负载分发到多台计算机或计 算机集群的过程。与故障转移不同,负载均衡的主要目的是提高性能, 例如提高响应速度,使系统能够处理更多的并发请求等。 在负载均衡配置中,通常存在多个活动的服务器。工作负载会根据 指定的负载均衡算法被分发到这些服务器中。当一台服务器发生故障 时,在适当的配置下,后续的请求会继续分发到其他可用的节点。因 此,在提高性能的同时,负载均衡机制避免了因为单点故障而可能造成 的系统失败,所以也提高了整个系统的可用性。 5.共享存储 共享存储(Shared Storage)是可以由同一网络内的多个服务器同时 访问的存储。 常用的共享存储包括共享文件系统(file system)存储和共享块 (block)存储。共享文件系统存储通常由网络附加存储(Network Attached Storage,NAS)实现;共享块存储通常由存储区域网络 (Storage Attached Network,SAN)实现。 共享存储在帮助维护应用程序的可用性方面扮演着重要角色。如果 关键的数据或文件存储在服务器直连存储(Direct Attached Storage, DAS)上,当该服务器出现故障时,这些数据都将不可用。然而,如果 数据存储在共享存储上,当一台服务器出现故障时,还可以通过其他服 务器进行访问。这意味着,在集群配置中,可以在集群中的另一个节点 上重新启动依赖于这些文件和数据的应用程序。 此外,使用共享存储时,保持共享存储的可用性非常重要。这通常 涉及重复存储、同步机制、监控及共享存储故障转移等技术。有兴趣的 读者可自行进行学习。 28.2 SAS高可用性方法概述 图28.1提供了SAS智能平台各层的组件所支持的高可用性配置模 式,以及相关的方法和技术。SAS智能平台的各层及其组件可参考第25 章的图25.1。 图28.1 SAS智能平台可用性概念和技术概要图 本节主要介绍常用的可为所有SAS组件提供高可用性的方法,包括 高可用集群(High-availabiity Cluster)和动态迁移(Live Migration), 以及SAS提供的涉及业务连续性的备份和恢复技术。关于其他可为多个 组件提高可用性的方法会在本章后续部分进行介绍。 28.2.1 高可用集群 在系统部分组件发生故障时,高可用性集群(HA集群)利用集群 中的冗余资源来保证系统仍然可以提供服务。HA集群通常用于关键数 据库、网络文件共享和业务应用程序。SAS服务器、spawners和其他服 务也可以部署在高可用性集群中,通过高可用集群提供的故障转移保护 可减少停机时间,从而提高可用性。 高可用集群可使用Platform EGO(SAS Grid Manager的组件)或其 他高可用软件实现,如Red Hat Enterprise Linux Cluster Suite、Microsoft Windows Cluster Services、Oracle Solaris Cluster、Veritas Cluster Server 等。高可用性软件安装在多台计算机上,可将这些计算机组建成一个高 可用性集群,以提供对应用程序或服务的保护。通过HA集群保护SAS 服务(或服务器)的过程如下: 1)HA软件通过操作系统服务或监控脚本监控SAS服务。 2)当HA软件监测到SAS服务状态不正常(因为硬件或软件故障) 时,HA软件尝试在当前主机上重新启动该服务。如果重启失败,HA软 件将SAS服务依赖的所有资源,例如文件系统、IP地址或集群名称等, 切换到集群中的另一台计算机,并在该计算机上重启服务。 3)当故障转移过程完成之后,来自客户端的请求被重定向到正在 运行SAS服务的计算机上。 在HA集群环境中安装和配置SAS时必须注意以下两点: ·当集群主机操作系统为Windows时,必须在集群中的所有主机上安 装SAS,且安装路径必须完全相同,然后在主节点上将SAS配置在所有 的集群节点都可以访问的共享存储中。 ·当集群主机操作系统为UNIX时,SAS安装文件可以安装在共享存 储中,以简化其安装和维护过程,也可以在多个节点上安装SAS。同 样,SAS配置文件也需存储在所有集群节点都可以访问的共享存储中。 通常,HA集群需要仲裁(quorum)设备。根据不同的HA软件,仲 裁设备可选择使用共享文件系统或共享磁盘的分区。 28.2.2 动态迁移 许多软件产品,常见的例如VMware VMotion和IBM POWER,提供 了基于虚拟化技术的动态迁移解决方案。动态迁移(Live Migration)会 透明地将正在运行的虚拟机移动到另一服务器,而不中断正在处理的工 作。 VMware VMotion是VMware vSphere产品套件的组件,可以将正在 运行的虚拟机从一台物理服务器实时地迁移到另一台服务器上,能够实 现零故障时间、持续服务,以及交易完整性。 IBM POWER系统和PowerVM虚拟化技术了提供动态分区迁移 (Live Partition Migration)技术,允许将分区从一个物理服务器迁移到 另一个物理服务器,且不会中断正在执行的应用程序。 28.2.3 SAS环境备份和恢复 备份(Backup)是指对重要数据或系统状态进行复制,以便将来用 于恢复数据或系统。恢复(Restore)是指通过备份将系统恢复到先前状 态的过程。 1.SAS部署备份和恢复工具 为了保证安装和配置后SAS智能分析平台在系统运行时的完整性, 需要建立正式的定期备份。在SAS 9.4中,SAS的部署备份和恢复工具 (SAS Deployment Backup and Recovery Tool)提供了集成的备份和恢 复SAS内容的方法。该工具将自动备份如下组件: ·SAS元数据服务器,包括元数据存储库、存储库管理器,以及元数 据服务器配置文件。SAS元数据服务器本身带有备份功能,SAS部署备 份和恢复工具就是调用该功能实现对上述元数据服务器相关文件的备份 的。 ·SAS每个服务器的Data目录、SAS Environment目录,以及服务器 的配置目录。 ·SAS Content Server存储库。 ·由SAS Web Infrastructure Data Server管理的所有数据库。也可以通 过更改备份配置来选择备份哪些数据库。 ·自定义目录。除了上述备份内容外,在该工具中还可以定义其他 需要备份的目录。 请参考SAS 9.4Intelligence Platform:System Administration Guide的 第12章Best Practices for Backing Up and Restoring Your SAS Content获取 更多信息。 2.元数据服务器备份工具 SAS元数据服务器提供了基于服务器的备份工具,该工具会根据预 定周期自动对元数据服务器进行备份。SAS的部署备份和恢复工具就是 调用该工具对元数据内容进行备份的。 请参考SAS 9.4Intelligence Platform:System Administration Guide的 第13章Backing Up and Recovering the SAS Metadata Server获取更多信 息。 3.导出/导入向导和导出/导入批处理工具 除了上述备份工具提供的备份方法,SAS导出/导入包向导和导出/ 导入批处理工具可用于对SAS文件夹或SAS文件夹中的SAS对象进行备 份和恢复。在一些SAS应用程序中,例如SAS Management Console、 SAS Data Integration Server中,可选择要备份的SAS对象,将其导出为 SAS包,需要时还可将其导入SAS系统,以实现备份和恢复功能。此 外,SAS对象导出/导入功能还可用于SAS对象从测试环境到生产环境的 迁移等。 请参考SAS 9.4Intelligence Platform:System Administration Guide的 第12章Best Practices for Backing Up and Restoring Your SAS Content获取 更加详细的信息。 28.3 SAS元数据服务器 在SAS 9.4之前,SAS元数据服务器的高可用性只能通过高可用集群 提供。从SAS 9.4开始,SAS提供了SAS元数据服务器集群(SAS Metadata Server Clustering)机制来提高SAS元数据服务器的可用性。同 样,在SAS 9.4中,HA集群仍然可用于提供SAS元数据服务器的故障转 移保护。 本节首先介绍SAS元数据服务器集群,接着对元数据服务器集群和 HA集群进行简单对比,最后给出了当元数据服务器单独部署时,在该 层安装的其他两个组件,这两个组件可用于提高可用性。 28.3.1 元数据服务器集群 SAS元数据服务器集群是由3个或多个主机机器(节点)组成的集 群。集群中的所有节点都被配置为同等的元数据服务器。当集群中的元 数据服务器启动时,节点彼此建立通信,其中一个节点变成主节点,协 调集群中的活动,而其他节点为从(Slave)节点。客户端应用程序与 集群交互的方式与未配置为集群的元数据服务器相同。主节点会使用轮 询算法将来自客户端应用程序的连接分发到各个从节点。 集群正常工作需要集群中的多数节点处于活动状态(指要求多数节 点上元数据服务器实例处于活动状态)。这称为仲裁(Quorum)规 则。在多节点的集群中: ·如果一个节点停止运行后仲裁规则仍然满足,那么元数据服务器 可以继续正常工作。到停止运行的节点上的连接会自动转移到其他活动 的节点。 ·当活动的节点不足以满足仲裁规则要求的节点数时,SAS元数据服 务器会暂停,并进入离线状态,现有的连接会保持,但是不能通过这些 连接访问元数据,而且新的连接不被允许。一个有3个节点的集群可以 容忍一个节点的失败。 SAS元数据服务器集群可以在初始安装和配置时进行配置,也可以 在配置一段时间后,再添加两个或两个以上的节点,将现有非集群的元 数据服务器转换成集群的元数据服务器。 SAS元数据服务器集群目前只支持Windows和UNIX操作系统,且集 群中的所有节点之操作系统必须相同。 28.3.2 提高元数据服务器可用性 SAS元数据服务器集群和HA集群都可用于提高元数据服务器的可 用性。表28.1比较了这两种方法的主要特征。 表28.1 SAS元数据服务器集群与HA集群特征比较 对于表28.1中各项的解释如下: ·SAS元数据服务器集群中存在多个元数据服务器实例,主节点会使 用轮循算法将来自客户端的连接请求分发到各个从节点上;而在HA集 群中,当前仅一个服务器实例响应请求,仅当该服务器故障时,该服务 才会自动迁移到备用节点。 ·SAS元数据服务器集群不依赖于任何第三方的软件;但HA集群需 要使用HA软件来创建集群。 ·在两种方法中,都要求集群满足仲裁规则。在SAS元数据服务器集 群中,最少需要3个节点,此时,如果一个节点或节点上的服务器发生 故障,活动实例和故障实例会形成2:1的局面,该集群仍然正常工作。 相比较而言,在HA集群中,是可设置额外的仲裁设备(根据不同的高 可用软件,仲裁设备可以为共享磁盘分区或者共享文件系统)来参与集 群的,所以运行SAS服务的最小节点数可以为两个。 ·配置SAS元数据服务器集群时,元数据备份目录必须在集群中的各 个节点上都可以访问;而高可用集群则至少要求将SAS配置文件配置在 共享存储上。 28.3.3 公共组件 在SAS智能分析平台中,SAS部署向导会默认在每台机器上安装 SAS Environment Manager Agent和SAS Deployment Agent。HA集群可以 为这两个组件提供可用性。 1.SAS Environment Manager Agent 在SAS服务器和中间层机器上都会配置SAS Environment Manager Agent的一个实例。Agent实例与SAS Environment Manager服务器通信, 它会检测每个代理所在机器上的资源,并周期性地将资源度量值报告给 服务器。由于不是关键服务,一般可不配置高可用性。如果需要,可使 用HA集群为其提供故障转移保护。 2.SAS Deployment Agent 在创建新的SAS服务器、配置SAS Web Application集群,以及使用 SAS部署备份和恢复工具进行备份和恢复SAS内容时,需要用到SAS Deployment Agent。一般情况下,该代理不需要一直保持运行,只需要 在执行上述管理性活动时启动即可。如果要求配置其高可用性,可使用 HA集群提供故障转移保护。 28.4 SAS计算层 本节主要描述可为SAS智能分析平台计算层各组件提供高可用的方 法,包括SAS计算服务器的负载均衡机制、SAS网格计算等。此外,本 节后面还介绍了当软件或硬件发生故障导致作业运行失败时,可帮助提 高作业重新提交执行效率的选项。 除了本节介绍的方法外,HA集群还可为该层的SAS服务器提供故 障转移保护。 28.4.1 SAS计算服务器负载均衡 SAS提供了配置选项来启用Workspace Server、Pooled Workspace Server、Stored Process Server和OLAP Server的负载均衡机制。这些服务 器的负载均衡可以在SAS初始安装和配置之后,再根据需要进行配置。 虽然负载均衡在跨多个服务器分发工作负载时,其主要目标是提高系统 性,但是当其中一个服务器故障时,工作负载就会被分发到其他可用的 服务器上,这也就提高了该服务器的可用性。 SAS Workspace Server、Pooled Workspace Server、Stored Process Server的负载均衡是由与这些服务器相关联的SAS Object Spawner处理 的。而SAS OLAP Server的负载均衡由SAS OLAP Server自行处理。 为这些服务器配置负载均衡的过程如下: 1)为每个计算服务器和spawner创建多个实例。 2)在另一机器上安装对应SAS组件,并修改一些配置文件。 3)将该计算服务器转换为负载均衡模式,并设置负载均衡算法。 配置过程完成后,工作负载也会根据指定的负载均衡算法分发到这 些实例上。当一个服务器实例故障时,提交的作业会在其他健康的服务 器实例上执行。这些计算服务器的可用性通过这一过程得到了提高。 详细配置过程可参考SAS Intelligence Platform:Application Server Administration Guide文档中相应的章节。 28.4.2 SAS网格计算 SAS网格计算(Grid Computing)环境是在SAS Grid Manager(SAS 网格管理器)的控制下,可将SAS计算任务分布到网络上的多个计算机 环境上。因为SAS网格是配置在一个多机器的体系架构中运行的,所以 不存在单点故障。作业可在当前可用的网各节点上进行处理,如果单个 节点不可用,其他节点仍然可以为后续计算和分析请求提供服务。 此外,在SAS网格环境中配置SAS计算服务器负载均衡时,所使用 的是SAS网格负载均衡算法。该算法使用软件Platform LSF来识别最空 闲的节点,然后将工作负载发送到该节点。 SAS Grid Manager中包含了Platform LSF。经过适当的配置, Platform LSF的组件Platform EGO可以像其他HA软件一样为SAS服务提 供故障转移的能力。 详情请参考SAS Grid Computing in SAS文档。 28.4.3 提高计算层组件可用性 SAS计算服务器为SAS智能平台提供了计算、数据服务,以及其他 相关的分析功能。本节介绍可为SAS计算层组件提高可用性的技术和方 法。除本节介绍的提高可用性的方法外,HA集群也可以为SAS智能平 台的所有组件提供故障转移保护。 1.SAS Workspace Server、Pooled Workspace Server、 Stored Process Server和SAS OLAP Server 标准的Workspace Server和Pooled Workspace Server可以执行由SAS Data Integration Server或SAS Web Report Studio等SAS客户端产生的代 码。Stored Process Server执行SAS存储过程。SAS OLAP Server执行并处 理MDX(Multidimensional Expressions Language,多维表达式语言), 从而实现查询立方体的功能。 SAS Workspace Server、Pooled Workspace Server、Stored Process Server由SAS Object Spawner初始化。Object Spawner监听请求并在需要 时启动对应的服务器响应请求。SAS OLAP Server直接处理MDX查询, 并从OLAP立方体中返回数据。这些服务器允许多个会话同时执行, SAS计算服务器负载均衡配置避免了因单点故障造成的服务中断,从而 提高了这些服务器的可用性。 HA集群也可用来为这些服务器提供故障转移保护,以提高可用 性。无论选择哪种方式,共享存储都是必要的。当为SAS Workspace Server、Pooled Workspace Server和Stored Process Server配置高可用性 时,作业、源数据、目标数据等需要使用的资源都会配置在共享存储 中,以保证集群中的所有主机都能够访问这些资源。当配置OLAP Server的高可用性时,立方体的物理文件必须存放在共享存储中。此 外,如果需要对立方体钻透(Drill-through),必须要将事实表也存放 在共享存储中。 2.SAS/CONNECT服务器 SAS/CONNECT服务器可以上传或下载数据,以及执行从远程机器 提交的SAS代码。SAS/CONNECT Spawner运行在远程机器上,它监听 来自SAS/CONNECT客户端的连接请求,并在本机上启动SAS进程响应 该请求。 可以在多台机器上配置多个SAS/CONNECT Spawner。而且,在 SAS网格环境中,客户端可选择将请求发送到SAS网格服务器上,网格 服务器会再根据各网格节点的工作负载情况分发任务。 28.4.4 作业运行选项 当任何SAS组件发生故障导致SAS作业(也称SAS任务或SAS程 序)运行失败时,通常必须手动重新提交失败的作业。这时,SAS程序 会从头再次开始运行。对于处理大规模数据与复杂元算的作业来说,完 全重新运行整个作业可能会比较费时,而且也没有必要。SAS的检查点 (Checkpoint)和重启(Restart)功能可以使运行失败的程序从上次失 败的地方开始运行。通过SAS系统选项或SAS Grid Manager Client Utility(在网络环境中)参数可以启用该功能。 1.SAS检查点和重启系统选项 SAS检查点和重启(Checkpoint-restart)功能可以通过向批处理程 序添加系统选项来启用。当启用该功能时,如果批处理程序在运行完成 之前被终止,则当该批处理程序被再次提交时,SAS会从上次运行失败 的步骤开始运行,而不必从头开始。 可根据PROC步和DATA步的边界,或在程序中添加的标签来划分 程序代码的执行单元。也就是说,可根据PROC步和DATA步的边界或 者标签来启动该功能。但是不能同时启动这两种类型的功能。 系统选项STEPCHKPTLIB、STPCHKPT和STEPRESTART启用 DATA步和PROC步边界的检查点及其相关功能,而系统选项 LABELCHKPT、LABELCHKPTLIB和LABELRESTART是用于启用标 签检查点等相关功能的。 这些系统选项及其相关代码通常需要手动添加到作业或批处理程序 代码中。而在网格环境中,若指定对应的SAS Grid Manager客户端实用 程序,即SASGSUB参数,SAS会自动向SAS代码中添加这些系统选项及 代码。接下来会介绍在作业运行失败后,SASGSUB中可使用的选项。 注意,如果在包括多个计算节点的集群中使用此功能,由 STEPCHKPTLIB或LABELCHKPTLIB指定的逻辑库必须位于集群中所 有节点都可访问的共享存储中。 2.SAS Grid Manager客户端实用程序 SAS Grid Manager客户端实用程序是一个命令行工具,命令为 SASGSUB。用户可使用该工具提交SAS程序到网格上进行处理和运 行。它可以与Checkpoint-restart功能一起使用,指定重启作业时是从最 先失败的过程步(DATA步或PROC步)重新开始运行,还是从最先失 败的标签处重新开始运行。同时还可利用Platform LSF的队列策略将发 生故障的作业发送到在网格中的另一个主机继续执行。 (1)SASGSUB启用SAS Checkpoint-restart功能 ·在网格环境中,只有在使用SASGSUB命令执行SAS程序或调度网 格作业时,Checkpoint-restart才可用。如果使用其他应用程序将工作提 交给网格,该功能则不可用。 ·使用SASGSUB将SAS程序提交到网格时指定GRIDLRESTARTOK 或GRIDRESTARTOK参数即可启用Checkpoint-restart功能。 GRIDLRESTARTOK参数用于启用标签检查点,GRIDRESTARTOK用 于启用PROC步和DATA步检查点。不能同时指定这两个参数。 ·需要注意的是,使用此功能时要求临时逻辑库WORK在共享存储 中,并且它还会增加了一些额外的资源开销,因此不建议运行每个SAS 程序时都使用。 (2)Platform LSF的重新排队机制 ·在网格环境中,可以设置队列,让运行结束时返回指定返回码的 作业,或让由于主机故障终止的作业自动重新排队并分发。如果作业正 在运行时主机和系统发生故障,该机制可确保所有失败的作业自动分发 到网格中的另一节点上。 ·该功能必须与GRIDRESTARTOK或GRIDLRESTARTOK参数一起 使用。 28.5 SAS中间层 SAS中间层包括SAS Web Server、SAS Web Application Server、 SAS应用程序、SAS JMS Broker、SAS Cache Locator、SAS Environment Manager,以及SAS Web Infrastructure Platform Data Server等。SAS Deployment Wizard(SAS部署向导)可将Web Application Server配置为 集群,而其他组件则需要手动配置来提高其可用性。 28.5.1 SAS Web Application Server集群 SAS部署向导可以在一个或多个中间层机器上自动配置多个相同的 Web应用程序服务器实例。配置在同一台机器上的多个服务器实例称为 垂直(Vertical)集群,配置在不同机器上的多个服务器实例称为水平 (Horizontal)集群。当SAS Deployment Wizard将SAS Web Application Server配置为集群时,也会默认将SAS Web Server配置为对SAS Web Application Server实例进行负载均衡的HTTP服务器。这时,SAS Web Server会将进入的请求分发到多个Web应用服务器实例上进行处理。 当中间层机器硬件有足够能力运行额外的服务器实例时,垂直集群 可以帮助改善性能,并且可防范软件故障。当一个Web应用程序服务器 实例崩溃,或一个服务器实例中的Web应用程序发生故障时,该应用程 序可在其他Web应用程序服务器实例中运行,并且可以向外提供服务。 水平集群在改善性能以及防范软件故障的同时,也能够防范硬件故 障,从而能够提供更高的可用性。如果一台机器崩溃,在其他机器运行 的Web应用程序仍然可以向外提供服务。 垂直集群和水平集群都可以避免由于SAS Web Applicatin Server的 单点故障而导致的系统失败,从而提高SAS Web Applicatin Server和SAS web应用程序的可用性。 28.5.2 提高中间层组件的可用性 本节介绍可为SAS中间层组件提高可用性的技术和方法。除本节介 绍的提高可用性的方法外,HA集群还为所有的组件提供了故障转移保 护。 1.SAS Web Server SAS Web Server是基于HTTP的Web服务器。负载均衡硬件设备、 负载均衡软件、轮询DNS,以及高可用集群都可为SAS Web Server消除 单点故障,从而提高其可用性。若负载均衡硬件、负载均衡软件和轮询 DNS为主动/主动设置,即可同时配置多个相同的Web服务器实例,工作 负载会分发到这些服务器实例上。 2.SAS Web Application Server和SAS Web应用程序 在28.5.1节中介绍的SAS Web Application Server集群在为SAS Web Application Server和SAS应用程序提高性能的同时,还能够消除单点故 障,以提高SAS Web Application Server以及SAS Web应用程序的可用 性。 值得注意的是,由于面向数据分析的特点和要求,SAS的很多Web 应用程序都会采用会话关联(session affinity),也称为黏性会话 (sticky sessions)。该特性可确保一个连接建立后,该连接的所有后续 请求总是分发到相同的Web应用服务器实例。因此,当一个Web应用服 务器实例故障后,故障的服务器实例上的会话将无法迁移到集群中的其 他服务器实例上。从Web客户端来看,请求会被重新定向到SAS登录页 面。用户可以重新登录到新服务器实例上工作,但是先前未保存的工作 会丢失。 3.SAS JMS Broker SAS中间层软件使用JMS Broker提供JMS(Java Messaging Services)服务。SAS JMS Broker是基于Apache Active MQ的。Apache 文档提供了多种策略提高其可用性,例如主/从(Master/Slave)集群、 Broker网络,以及上述两种方法的组合。当为JMS Broker配置了高可用 性时,要对SAS Web Application Server进行相应的配置。 SAS Intelligence Platform:Middle-Tier Administration Guide提供了 基于共享文件系统的主从配置指导。 4.SAS Cache Locator SAS Cache Locator是基于Vmware vFabric GemFire的。SAS Web Application Server和SAS Web Infrastructure Platform的预定服务需要使用 到该组件。在单机部署中,默认仅配置一个Cache Locator;但是在SAS Deployment Wizard配置SAS时,如果为Cache Locator和预定服务的 Cache Locator指定了不同的端口,则会配置两个Locator。而在多机部署 中,如果计算层与中间层分别部署在了不同机器上,则在中间层和计算 层机器上会各配置一个Cache Locator。 在上述两种情况下,经过一些手工配置,这两个Cache Locator可彼 此提供故障转移支持。这两个Cache Locator地位对等,若一个停止服 务,另一个将承担所有的工作。当然,也可专门为Cache Locator配置多 个实例以提高其可用性。 5.SAS Web Infrastructure Platform Data Server SAS Web Infrastructure Platform Data Server(SAS Web Infrastructure Platform数据服务器)为SAS Web Infrastructure Platform、SAS Content Server和SAS Environment Manager提供存储。默认情况下,该服务器基 于PostgreSQL。 可以为Web Infrastructure Platform Data Server配置主服务器和备用 服务器。PostgrepSQL的流式复制(streaming replication)机制能够将记 录持续地发送到备用服务器以保持同步。流式复制不提供识别主服务器 故障和通知备用服务器的功能。然而,有些工具可以很好地与操作系统 功能(例如IP地址迁移)集成来提供故障转移。 6.SAS Environment Manager SAS Environment Manager合并了一些Vmware Hyperic技术以提供企 业级的操作功能,提供了Web方式的SAS环境管理工具,例如对SAS资 源的监控和管理工具。 可以设置一个Hyperic服务器集群来启用SAS Environment Manager 的高可用性。在该集群中,Hyperic会自动选择一台服务器作为主节 点,其他节点则作为备用节点。备用节点不分担工作负载。Hyperic服 务器集群需要通过负载均衡软件或硬件将客户端请求发送到主Hyperic 服务器。 28.6 数据层 对构建可靠的系统来说,数据存储的可用性也是需要考虑的内容。 SAS智能分析平台的数据有多种存储方式。维持数据的可用性时,根据 存储方式的不同会有所不同。 SAS数据集中的数据、SAS OLAP立方体及SAS SPD引擎表都是通 过文件系统来存储和访问的。其可用性需从文件系统的角度进行考虑。 对于关系型数据库或ERP系统中数据的可用性,可通过关系型数据库或 ERP系统的可用性技术来提高。无论是哪种方式,底层存储是物理存 储,所以也可以通过对磁盘设置RAID来提供冗余。 28.7 本章小结 SAS智能平台的所有组件都可使用合适的方法和技术来提高其可用 性。在具体方案设计时,有如下几点可参考: ·不需要为所有组件进行高可用配置。本章虽然描述了可提高SAS智 能平台各个组件可用性的方法,但是对一些非关键服务,例如SAS Environment Manager Agent和SAS Deployment Agent等,则不必为其专 门配置来提高可用性。 ·SAS计算服务器可使用其自身的负载均衡机制或HA软件来提高其 可用性。只是在进行负载均衡配置时需要更多的SAS软件许可,但同时 也能够增加整个系统的分析和处理能力。 ·对SAS组件进行高可用配置时,需整体规划,例如,从可用的硬件 (机器和共享存储)、组件分层、关键服务等各方面综合考虑。