news 2026/10/15 11:32:17

SAS零基础入门:掌握DATA步与PROC步,搞定数据清洗与统计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
SAS零基础入门:掌握DATA步与PROC步,搞定数据清洗与统计

简介:面向零基础读者的SAS入门电子书,以“小白”学SAS的对话体展开,从解决实际问题出发,不按命令语法依次罗列,适合高校学生、职场新人和需要处理数据报表的分析人员。内容覆盖建立数据集、常用函数、数据清洗、统计绘图与制表等核心模块,并结合案例讲解如何用count函数分析商品评价、结合数组编写查找缺失值的万能程序、利用round函数对变量自动分组等实战思路;制表部分还涉及表格修饰和直接输出规范三线表。全书强调理解每个操作背后的分析逻辑,而不是死记命令,并提供查找重复值、缺失值和异常值的数据清洗思路。资源为1个PDF文件,压缩包整体约1.76MB,便于直接阅读和反复查阅。目前已有707人学习下载,是快速建立SAS整体认知、降低上手门槛的实用参考书。

1. 小白学SAS基础教程:为什么这门外语值得花三个月

拿到这个标题的读者,多半不是冲着「了解」来的,而是被具体场景逼到门前:想投医药行业统计岗位,招聘说明上写着 SAS;进了银行风控或保险精算团队,同事丢来一个 .sas 文件让你跑通;或者用惯了 Python,忽然要对接一套走完审计流程的旧系统。这些场景有个共同点——SAS 不是说更高级,而是这些行业默认的「流程语言」,结论和数据都挂在它上面。这篇笔记不灌鸡汤,就讲清一件事:SAS 怎么从零学起、怎么把一张表读进来、怎么算、报错怎么看。你会发现它的语法真不难,难的是先换个思维方式——把一切分析拆成「DATA 步建表、PROC 步干活」两条腿走路。下面按这条路一步步走通。

2. 装好环境跑通第一个程序:SAS 的两种步和看得懂的日志

2.1 单机客户端、网页版和云端免费环境怎么选

买教程回来先看环境的人不在少数,因为 SAS 的安装确实是劝退重灾区。常见三种:企业常用的单机客户端版,功能全但要装几十 GB、许可证不便宜,个人没必要;网页版在浏览器里写代码,免安装,有语法提示,适合入门;云端教学实例是官方提供的免费学习环境,注册后直接用,自带示例数据集,是目前自学人群的主流选择。

环境优点缺点适合谁
单机客户端版与企业真实环境一致、离线可用安装慢、许可证贵公司已采购、需要长期离线的人
网页版免安装、界面友好、提示清晰依赖网络、服务器不在本地刚入门的新手
云端教学实例注册即用、有示例数据网络不稳时连接会断、存储有限学生和自学人群

选型建议很直接:新手从云端教学实例或网页版起步,别一上来就折腾客户端。等你把下面的例子跑通了,再考虑本地环境也不迟。很多人学 SAS 的第一道坎不是语法,而是花了两天装环境最后放弃,这属于典型的投入顺序错了。

2.2 第一个跑得通的程序:看懂步、数据集和日志

环境就绪后,先跑一个最小程序。SAS 程序由「步」组成,最常见的是 DATA 步和 PROC 步:DATA 步负责建表、清洗,PROC 步负责分析和输出。下面这个程序把两步都包含了。

/* 第一个 SAS 程序:先看语句结构 */ data mydata; /* DATA 步开始,创建数据集 mydata */ x = 1; y = 2; total = x + y; /* 新变量 total 由计算得到 */ run; /* 执行并结束 DATA 步 */ proc print data=mydata; /* PROC 步:打印数据集内容 */ run;

逻辑说明:data 关键字后面跟数据集名,步内用赋值语句创建变量,x、y、total 都是运行时动态生成,不需要事先声明。run; 是步的结束标记,也是触发执行的动作。proc print 把数据集按表格打印出来,data= 指定要打印的数据集,省略时默认处理最近生成的数据集。

参数说明:数据集名不能以数字开头,长度不超过 32 个字符,不能用汉字和空格;变量命名规则相同。这里生成的数据集存放在名为 WORK 的临时逻辑库里,本次会话结束就没了,想长期保存需要另建逻辑库(libname 语句),新手阶段先不用管。

提交运行后,界面会分两个区:结果区和日志区。日志区的三种信息级别必须分清:

信息级别含义处理方式
NOTE正常提示,比如创建了多少观测阅读即可
WARNING有潜在隐患但程序继续跑留意并回顾
ERROR语句出错必须处理后才能继续

刚入门最容易把 NOTE 当报错,看到一片绿色提示就慌。记住:只有以 ERROR 开头的内容是真正的错误,WARNING 是提醒,NOTE 是家常便饭。你最终要养成的习惯是「提交前看日志,日志里找 ERROR」,而不是盯着结果区看。

2.3 变量类型和缺失值:两个必须先建立的直觉

SAS 的变量只有两类:数值型和字符型,日期本质上是数值型。字符变量的值必须用引号包起来,数值变量不能用引号。新手写代码时最常见的低级错误,就是把数字 23 写成 '23',这样它就成了字符,算均值时直接被跳过,而且日志可能只给一个不起眼的 NOTE。

data demo; a = 10; /* 数值变量 */ b = .; /* 数值缺失值,写一个点 */ c = ""; /* 字符变量,值为空字符串 */ d = '01JAN2024'd; /* 日期字面量:单引号+日期+字母 d */ run;

逻辑说明:数值缺失值写成 .,参与运算时会被跳过,比如 mean 函数只对非缺失值求平均;字符缺失是空字符串 '',两个概念别混。d 是日期字面量写法,SAS 内部把它存成数值——从 1960-01-01 起算的天数,所以日期加减就是普通数值运算。常见坑是漏掉末尾的 d,日期就会变成字符文本,后面排序、区间判断全乱。

参数说明:日期显示默认是数字(如 23390),要显示成人们能看的格式需要配合 format 语句,比如 format d date9.;。新手阶段只要记住「日期要加 d、显示要用 format」这两条就够了,等做到时间序列再深入。

3. DATA 步把数据读进来:从零构造数据集并完成清洗

3.1 用 DATALINES 构造小数据集:读入逻辑与三个必设参数

真实工作里数据大多来自 CSV 或数据库,但自学阶段强烈建议先会「在程序里造数据」。DATALINES(老写法叫 CARDS)把数据直接嵌进程序里,好处是程序自包含、别人拿到就能跑、报错了也好复现。先用它把读入逻辑练熟,再去接外部文件。

data raw; infile datalines dlm=","; /* 数据源是程序内嵌行,分隔符为逗号 */ input id name $ age score; /* 按顺序定义:id 数值,name 字符,age/score 数值 */ datalines; 1,zhang,23,88 2,li,25,76 3,wang,24,. 4,liu,22,91 ; run;

逻辑说明:infile datalines 声明数据行就在程序里;dlm="," 指定分隔符,不写默认按空格分隔,如果数据是 CSV 就必须写。input 语句按数据行的顺序定义变量:先写变量名、再写字符标记、继续下一个变量。name 后面的 $ 表示这是字符变量,漏掉 $ 是最常见的读入错误——SAS 会把 zhang 当成数值去读,直接报错。

参数说明:dlm 只能指定单字符分隔符,tab 分隔要写成 dlm="09"x 这种特殊写法,新手先跳过;缺失值直接写 .,SAS 自动识别为数值缺失。读入成功的标志是日志里出现类似 NOTE: Dataset WORK.RAW has 4 observations and 4 variables,看到这一行再往下走。

实际工作读外部 CSV 时,把 infile 换成文件路径,并加一个参数:

data raw; infile "/tmp/data.csv" dlm="," firstobs=2; /* 跳过第一行表头 */ input id name $ age score; run;

firstobs=2 表示从文件第 2 行开始读,用于跳过 CSV 的列名行。注意路径要写服务器或本机的实际路径,网页版环境里路径含义和你本机不一样,这也是新手经常「文件找不到」的原因。另外别在数据行里混入中文逗号或引号,DATALINES 模式下这类字符会让读入错位,后面日志显示的观测数会少得让你怀疑人生。

提示:DATA 步里新建的变量不要和 SET 读入的变量重名,否则日志会给出变量冲突的报错,把名字换掉即可。

3.2 清洗三件套:WHERE 过滤、RENAME 改名、IF 分组

原始数据进 SAS 之后,第一轮操作基本都是清洗:过滤无关行、改一个顺手的变量名、按条件生成新分组。这三个操作几乎是每个程序里都会出现的,把它们练成肌肉记忆。

data clean; set raw; /* 逐行读入 raw 数据集 */ where age >= 23; /* 读取时过滤:只保留年龄不低于 23 的行 */ rename score = final_score; /* 将 score 改名为 final_score */ if age = 23 then group = "young"; else group = "other"; /* 生成新字符变量 group */ keep id name final_score group; /* 输出时只保留四列 */ run;

逻辑说明:set raw 把上一步的数据集逐行读入;where 在观测进入处理流程之前过滤,效率高,但它只能判断原数据集里已经存在的变量。rename 的写法是把旧名换成新名,注意改名动作发生在输出阶段,所以在步内部你还要继续用旧名 score 做判断。if/then 是条件赋值,else 处理其余情况。keep 控制输出列,等价的还有 drop。

参数说明:keep 和 drop 只能二选一,不要同时用,逻辑上会打架;where 里不能引用本步刚生成的变量,比如上面不能写 where group="young",因为 group 是后面才生成的。想按新分组筛行,要用子集化 IF——不带 then 的 if:

data young; set raw; if age >= 23; /* 满足条件才保留该观测 */ run;

子集化 IF 在语句执行到这一行时判断,不满足就丢弃当前观测,适合「先赋值、后筛选」的场景。这是和 where 最容易混的点,记住一个原则:筛原表已有变量用 where,筛新生成变量用子集化 IF。

3.3 横向拼接与纵向拼接:MERGE 前为什么必须排序

两张表合成一张是数据整理的高频需求。纵向拼接是把观测堆在一起,用 SET 同时读入多个数据集;横向拼接是按某个键把变量并到同一行,用 MERGE。纵向拼接很简单:

data all; set ds1 ds2; /* 依次把两组观测首尾相接 */ run;

set 后面跟多个数据集,按顺序拼出所有行,变量以第一个数据集为准,后面数据集里多出的变量也会自动补进来。横向拼接就复杂一些,必须先排序:

proc sort data=student out=student_s; by id; run; proc sort data=score out=score_s; by id; run; data merged; merge student_s score_s; by id; run;

逻辑说明:merge 按 by 变量把两边对齐,类似数据库里的 join。关键前提是参与 merge 的每个数据集都必须先按 by 变量排好序,否则 SAS 不会帮你排序,而是按物理顺序硬拼,结果是观测数对不上、字段错位,而且不报错——这类问题排查起来最费时间。

参数说明:proc sort 的 out= 用于指定输出数据集,避免覆盖原表,这是强烈建议保留的习惯;by id 是升序,多个变量按顺序写 by a b。如果两边数据量不一致、有些 id 只在一边出现,可以用 in= 变量控制匹配方式:

data merged; merge student_s(in=s) score_s(in=c); by id; if s and c; /* 只保留两边都存在的 id,相当于内连接 */ run;

in= 会在每个观测上打标记:1 表示该观测来自对应数据集,0 表示不是。if s and c 只保留两边都匹配的行,改成 if s 就是左连接。这个技巧每个做清洗的人都该背下来,它是 merge 的灵魂。如果遇到一对多、键不唯一的情况,merge 可能出现行爆炸,那时候建议改用第 4 章的 PROC SQL 做 join,控制起来更直观。

4. PROC 步把数据送出去:四个高频过程步够应付八成需求

4.1 PROC SORT 与 PROC PRINT:把数据排好、看清楚再做分析

DATA 步建完表,第一步永远是打印出来看,确认数据跟自己想的一样。SORT 负责排序、PRINT 负责显示,这两个过程步总是连着出现。

proc sort data=clean out=clean_s; by descending final_score; /* 按 final_score 降序排列 */ run; proc print data=clean_s noobs; /* noobs:不显示最左侧的行号列 */ var id name final_score; /* 只打印这三列,顺序就是 var 的顺序 */ where final_score >= 80; /* 打印前过滤,不改变数据集本身 */ run;

逻辑说明:by 默认升序,降序要在变量名前写 descending,注意 descending 只作用于紧跟着它的那一个变量,多个变量要分别写。print 默认打印全部列,var 用于挑选列并决定显示顺序;where 在打印时过滤行,原数据集不受影响。

参数说明:noobs 去掉行号列,让输出更像正式报表;想在打印时给变量加显示格式,可以在 var 后写 format 语句。同时按两个变量排序,写 by group final_score,表示先按 group 升序、组内再按 final_score 升序。经常有人排完序发现观测顺序变了以为自己写错,这不是问题,排序本来就是要改变物理顺序。

排错技巧:打印结果为空时,先检查 where 条件是不是把行全滤掉了;再把 where 去掉打印一次看全貌。空结果在数据步时期发现成本最低,等进了分析阶段才发现,回头查哪儿丢的行就麻烦了。

4.2 PROC MEANS 与 PROC FREQ:描述统计与交叉表一张纸出来

数据检查完,就要出描述统计。MEANS 出连续变量的统计量,FREQ 出分类变量的频数分布,这两个是汇报里出现频率最高的过程步。

proc means data=clean n mean std min max maxdec=2; var final_score age; /* 要统计的数值变量 */ class group; /* 按 group 分组,每个组各出一行统计 */ run;

逻辑说明:n 表示有效观测数(自动忽略缺失),mean 均值、std 标准差、min/max 是最小最大值;maxdec=2 把统计量统一保留两位小数,这是写报告最常用的参数。var 只能列数值变量,把字符变量放进去会直接报错。class 指定分组变量,分组后每一组出一行统计,相当于 SQL 的 group by。

参数说明:不带 class 就是整体一行;class 可以跟多个变量,比如 class group age 会做交叉分组。统计量关键字可以任意组合,写 n mean std median 就同时输出这四个。如果数据里有缺失值,n 和 mean 都基于非缺失值计算,这一点要和电子表格工具的写法区分开——有些电子表格函数会把文本当零,SAS 不会。

分类变量看分布用 FREQ:

proc freq data=clean; tables group group*final_score / norow nocol nopercent; run;

逻辑说明:tables 里的 group*final_score 是交叉表,行是 group、列是 final_score 的取值;三个选项 norow、nocol、nopercent 分别去掉行百分比、列百分比和总百分比,只保留频数,报表更干净。想看单一变量分布,直接写 tables group。

参数说明:省略 tables 语句时,FREQ 会对所有变量各出一张单变量表,变量多的时候输出量很大,不建议。交叉表适合两个分类变量;如果 final_score 是连续数值,FREQ 会把它当成离散值列很多列,这种情况先分组再交叉。

4.3 PROC SQL:会一点 SQL,数据整理效率翻倍

很多教程把 SQL 放得很靠后,但实际项目里 PROC SQL 的价值是能少写一半 DATA 步。它的核心优势是:聚合、连表一步写完,而且 join 不需要预先排序。

proc sql; select group, max(final_score) as best_score format=6.2, /* 别名 + 显示格式 */ count(*) as cnt from clean group by group; quit;

逻辑说明:proc sql 的结束标记是 quit 而不是 run,这一点第一次用的人基本都会踩。select 后面跟要输出的列,as 给计算列起别名,别名就是报表里的列名。count(*) 计行数,group by 分组,效果和 MEANS 的 class 一致。

参数说明:format=6.2 表示总宽 6 位、小数点后 2 位,是数值显示的控制写法;想把这句结果存成数据集,在前面加 create table summary as,不写就直接打印到屏幕。

比 DATA 步更省事的是 join 多表,不需要提前排序:

proc sql; create table joined as select s.id, s.name, c.score from student as s left join score as c on s.id = c.id; quit;

逻辑说明:from 后面的 as 给表起别名,on 指定连接键,left join 保留左表全部行。和 MERGE 的区别是少了排序这一步,多键连接也直接写在 on 里。对新手来说,遇到一对多和键不唯一的情况,SQL 的 join 比 MERGE 更容易预测结果。

注意点:SQL 里字符串比较加引号且区分大小写;子句写错时,日志报错位置往往指向 quit 前附近,所以在 sql 步里把每条子句单独成行、缩进对齐,排错会轻松很多。给新人的建议是:DATA 步的 SET/MERGE 必须会,因为它构建了对数据流的理解;日常汇总连表用 SQL,因为快。

5. 新手避坑:五条血泪经验与日志排查方法

5.1 漏写分号:SAS 错误日志里出现频率最高的坑

现象:提交后日志报 ERROR,但报错的行号明明不是自己写错的那一行,或者上一条语句把下面几行都「吞」了。

原因:SAS 用分号表示一条语句结束。分号漏掉后,解释器会把下一行当成上一条语句的延续,报错位置自然偏移,看起来完全对不上号。

解决:养成三步习惯——每写完一个步立刻补 run;/quit; 再写下一个步;提交前从最后一行往上看一遍行尾;日志报错行可疑时,先检查它的上一行末尾有没有分号。这类错误占新手报错的比例相当高,遇到先查分号,别急着改逻辑。

5.2 字符和数值混用:隐式转换让你算错还找不着北

现象:日志出现类似 NOTE: Character values have been converted to numeric values 的提示,结果里某些行变成缺失值,或者分组统计的组数突然变多。

原因:把 '23' 这种字符当数值比较、把数值变量塞进字符位置时,SAS 会尝试自动转换。一旦源数据里有非数字字符,转换失败就成了缺失值,而且过程不报 ERROR,只有一行不起眼的 NOTE。

解决:守住两条规则——数值变量绝对不加引号,字符变量必须加引号;需要类型转换时用显式函数,字符转数值用 input(x, 8.),数值转字符用 put(x, 8.)。看到日志里有 convert 字样,就先停下手里的分析,回去把类型理顺再继续。这类问题属于典型的「不算语法错,但结果全错」的翻车现场。

5.3 MERGE 不等值乱套:观测数不对先查排序

现象:merge 之后观测数既不是两表之和也不是最大表行数,字段错位,某些行出现类似笛卡尔积的膨胀。

原因:by 变量在两个输入数据集里没排序,或两边排序用的键不一样(一边 by id、一边 by id2),SAS 不做自动排序,直接按物理顺序硬拼。

解决:铁律——merge 前每个输入数据集都单独 sort,且用 out= 保留原表;检查 by 变量名完全一致;用 in= 变量加 if 语句做匹配筛查,能立刻定位哪些 id 没对上。在一对多场景里,如果匹配键不唯一,行数会成倍膨胀,这时候改用 PROC SQL 的 join 并仔细检查连接键是否唯一,比在 DATA 步里调 in= 更直观。

5.4 IF 与 WHERE 的差别:执行时机不一样,结果就不一样

现象:同样一个条件,写在 set 后面和写在步末尾,结果不同;或者用新生成的变量做条件时直接报错。

原因:where 在观测进入处理流程之前过滤,此时新变量还没生成,所以它只能用原变量;if 在步执行过程中按语句顺序判断,可以引用前面语句刚生成的变量。两者执行时机不同,不是同一个东西。

解决:按场景选——只过滤原表已有变量且追求效率用 where;需要在赋值之后再筛选用子集化 IF;在 merge 里判断匹配状态用 if + in=。还有一点,where 在 PROC 步里也能用(比如 print 里过滤行),if 只能出现在 DATA 步。把这条想清楚,很多「同样条件结果不同」的玄学就消失了。

5.5 日志被 NOTE 淹没:怎么从一片提示里找到真正的 ERROR

现象:日志刷屏,一大堆 NOTE 和 WARNING,程序已经停了或者还在跑,但你找不到关键报错在哪一行。

原因:日志默认输出所有提示级别,新手没设置过滤,也不习惯在长日志里定位,只能靠肉眼从上往下翻,效率极低。

解决:提交后直接按页内查找搜 ERROR,第一个 ERROR 才是根源,后面的经常是连锁反应;找到后再往上翻一小段,看紧挨着它的 NOTE/WARNING,那里通常写着变量未初始化或类型转换的细节。在步内用 options 减少回显可以辅助,但新手阶段最有效的是「先找第一个 ERROR,再往上读上下文」。任何一个报错,都先回答三问:在哪一句、哪个变量、什么操作——回答完再动手改,能省下大半排查时间。

6. 从「能跑通」到「敢交结果」:验证习惯和三分钟检查

6.1 用 20 行小数据给自己出题

自学最怕代码跑通但结果没人核对。我一般准备一份 20 行的模拟数据:5 个 id、一个分组变量、一个数值指标、再留两列缺失值。先用计算器手工算出每个组的均值和频数,再跑 PROC MEANS 和 FREQ 对比。这个方法比任何教程都管用——数据步写错、类型转错、缺失值被误处理,全部会体现在数值对不上里。对得上,再进入下一步分析。

6.2 提交前的三分钟检查

我带新人时会给一张贴在显示器边的清单:每个步的结尾有没有分号;run 或 quit 是否成对;where 用的是不是原表已有变量;字符变量在 input 里有没有写 $;merge 的 by 是否两边都排序过;打印前 10 行确认观测数。做完这几项再提交,报错率能降一半以上。这套动作不值钱,但能让你从「靠日志救火」变成「提交前就拦住」,养成习惯之后就再也不想裸奔了。

6.3 把结果导出成 PDF 文件的最小命令

ods pdf file="/tmp/check.pdf"; /* 打开输出目标 */ proc means data=clean n mean maxdec=2; var final_score; run; ods pdf close; /* 关闭输出目标,必须成对 */

这是最常用的导出三件套:打开、过程步、关闭。close 忘写会导致文件被占用,下次提交打不开,这个坑我亲眼见过好几次。后来我养成的习惯是:任何导出代码,先写上 open 和 close 一对,再往中间填过程步,顺序永远不会乱。

带过的新人多了以后,我发现学 SAS 真正难的从来不是语法,而是没有人告诉他哪些输出是可以信的。所以我把这章做成验证习惯而不是命令大全。你现在踩的每一个坑,都是将来能跟新人讲半天的素材;把这些坑记进自己的错误清单,比收藏任何教程都管用。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 11:32:01

嵌入式AES-256 C语言实现:轻量、恒定时间、可验证的生产级落地

简介:本资源是一份轻量级、可直接集成的AES-256对称加密算法C语言实现,面向嵌入式开发工程师、密码学初学者及需要在无第三方库环境下实现数据加解密的C/C开发者。它解决了在资源受限平台(如MCU)上自主实现高安全性加密的核心需求…

作者头像 李华
网站建设 2026/10/15 11:32:00

Agent-Reach实战:解决多智能体触达难题与组网实践

从“Agent 互联”到本质:Agent-Reach 的触达难题与组网实践这几年只要做 AI 应用,就一定绕不开多智能体协作。问题也几乎出在同一处:单个 Agent 能做局部任务,一旦要让多个 Agent 彼此发现、交换消息、按场景编排逻辑,…

作者头像 李华
网站建设 2026/10/15 11:31:37

Go实现PDF文字与图片提取:解析原理、OCR集成与工程避坑

1. 项目概述:为什么PDF内容提取不是“打开复制粘贴”那么简单你有没有遇到过这种场景:一份几十页的会议纪要PDF,领导让你把里面所有带“预算”“Q3”“交付周期”的段落摘出来整理成表格;或者某份产品说明书PDF里嵌了十几张高清原…

作者头像 李华
网站建设 2026/10/15 11:31:18

libcurl.zip源码构建指南:从压缩包到嵌入式HTTPS网络模块

简介:本资源是面向Android底层开发者的libcurl跨平台静态库集成包,专为NDK环境下的HTTPS/HTTP/FTP网络通信需求设计,适用于需在C/C层实现高性能、细粒度网络控制的中高级开发者。压缩包含36个文件,以27个头文件(.h&…

作者头像 李华
网站建设 2026/10/15 11:31:09

PCA图像压缩:原理、实现与MATLAB优化

1. 主成分分析在图像处理中的应用价值 主成分分析(PCA)作为一种经典的降维技术,在图像处理领域展现出独特的优势。当我们需要处理高维图像数据时,PCA能够有效提取关键特征,去除冗余信息,实现数据压缩。这种…

作者头像 李华