简介《小白学SAS基础教程》以PDF电子书形式提供面向零基础的数据分析初学者与需要处理大量数据的从业者采用对话体引导主人公「小白」从零起步解决SAS命令繁杂、教材难读的入门难题。压缩包仅含1个pdf文件约1.76MB轻量便携可随时查阅。全书聚焦数据整理、作图和制表三大部分涵盖数据集建立的基础与高级议题、常用函数的使用思路、缺失值与重复值的清洗方法、SAS绘图技巧以及规范报表与三线表输出并强调每个操作背后的实际业务含义而非单纯堆砌命令。目前已有709人学习关注。读者可从中获得一套可迁移的问题解决思路与现成可套用的清洗程序适合零基础入门与自学查漏补缺。1. 先看全景SAS 基础入门其实只有一条主线很多人对 SAS 的第一印象停在航空母舰这个词上——模块多、命令多、帮助文档厚得像字典于是还没开始就先怯了。把《小白学SAS基础教程》这类入门材料完整捋一遍会发现SAS 基础入门其实是一条很窄的主线数据怎么进来、怎么变干净、怎么变成图和表出去。整本书反复围绕的就三件事——数据整理、作图、制表其余都是配件。这条主线可以摊成四层。最底层是数据步data、input、cards 三句话决定数据以什么形态进入 SAS往上一层是输入输出格式与函数决定变量的样子和取值规则再往上是数据清洗与加工合并、查重、补缺失、挑异常这是实际工作里最耗时间的一段最顶层才是 proc sgplot 出图、proc tabulate 出表、ODS 把结果送到 Word 或 PDF。适合谁完全零基础、被帮助文档劝退、日常要交报表的人先认这张图再往里填细节效率比从头背语法高得多。2. 数据步data、input、cards 三句话撑起的建库基本功数据步是 SAS 的地基地基没打牢后面所有 proc 步骤都是在流沙上盖楼。这一章把建库、逻辑库、格式、新变量四件事按顺序拆开每一条都对应实际写程序时会被卡住的地方。梳理下来会发现新手在这个环节翻车的频率远高于函数和绘图原因往往不是不会写而是不知道规则在哪。2.1 三句话建库与命名的硬规则在 SAS 里直接录入数据结构固定得近乎刻板data 给数据集起名input 声明变量cards 宣告下面是数据最后 run 收尾。三句话之后必须有英文分号而数据行结束的那个分号必须另起一行——这一条看着琐碎却是新手最高频的报错来源数据直接跟在分号后面SAS 会把最后一行数据当成语句的一部分吞掉。data first; input gender age; cards; 1 30 0 24 ; run;这段程序里data 后面的 first 是数据集名input 后面按顺序列出两个变量名cards 之后每行一条观测两个值之间用空格分隔。SAS 默认以空格作为变量分隔符读到空格就认为当前变量读完所以数据行里多一个空格、少一个空格都会让变量错位。run 告诉 SAS 这一段可以执行了。数据集名和变量名的规则基本一致只能由英文字母、数字和下划线组成首字符不能是数字长度不超过 32 个字符不能含空格、中文和特殊字符。所谓特殊字符主要指键盘数字键上方那一排符号。有人会想到加一句options validvarnameany;让 SAS 认中文变量名技术上可行但后续写 proc 步骤、做宏、导数据时麻烦不断入门阶段不建议碰。提示变量名稳妥起见一律用英文字母加下划线别为了看得懂去用中文名或带点号的名字省下的理解成本远小于后面排查的成本。变量数量不用担心input 后面写几十个、上百个变量 SAS 都能处理真正的限制在数据本身的录入口径上。2.2 逻辑库Work 与永久数据集的关键差别数据集建完放到哪儿答案在资源管理器的逻辑库里。逻辑库不是一个文件夹而是 SAS 对物理路径的一层映射。打开逻辑库会看到若干个子库其中 Work 是留给用户放数据的空库其余如 Sashelp、Sasuser、Maps 系列都存着 SAS 自带的数据或帮助文件不该往里写东西。Work 库有一个致命特性关掉 SAS里面的内容自动清空。所以建在 Work 下的数据集叫临时数据集只适合当场练手。要让数据留到下次开机还能用必须指定一个永久库。libname mylib D:\sasdata; data mylib.class; set sashelp.class; run;libname 语句把库名 mylib 指向一个已经存在的物理目录库名本身不超过 8 个字符路径必须用单引号括起来且目录要事先建好SAS 不会替你创建。之后写mylib.class就是在那个目录下生成一个永久数据集文件。注意 libname 是会话级的每次重新启动 SAS 都要再执行一遍除非把它写进启动配置里。这一节最容易忽略的其实是我明明建过数据集怎么没了——十有八九是建在 Work 里然后关了软件。养成开头先 libname 的习惯比事后找后悔药划算。2.3 输入格式与输出格式informat 和 format 别混SAS 里格式分成两个方向混淆这两个词会让人读程序时完全看不懂在干什么。输入格式 informat 管的是读进来的时候怎么解释这串字符输出格式 format 管的是显示出来的时候长什么样。前者只在数据步读数据时起作用后者跟着数据集走任何 proc 打印时都按它显示。data demo; infile cards dlm,; input id name :$10. birth yymmdd10. score; format birth yymmdd10. score 8.2; cards; 1,A,1998-05-12,88.5 2,B,1999-01-03,92 ; run;这段里infile cards dlm,把分隔符从空格改成逗号适合从 CSV 复制过来的数据。input 中name :$10.的冒号是修饰符表示先跳过前导空格再按 10 位宽度读字符birth yymmdd10.把1998-05-12这样的文本读成日期数值——注意日期在 SAS 内部存的是一个数字format 只是让这个数字以日期样式显示。score 8.2表示总宽 8 位、保留两位小数。另一个修饰符用于读含空格的字符值它告诉 SAS 一直读到两个连续空格或行尾为止处理北京市 海淀区这类地址字段时很有用。日期类输入格式里yymmdd10.、mmddyy10.、date9. 各有对应写法选错格式不会报错只会得到一个离谱的数值。输出格式还可以自定义。proc format 定义一个取值到标签的映射之后用 format 语句挂到变量上proc format; value grp low - 59 不及格 60 - 79 及格 80 - high 优秀; run; data score2; set score; format total grp.; run;value 后面跟的是格式名区间用连字符low 和 high 代表上下不限。自定义格式的好处是原始数值不动只是显示变了做分组统计时可以直接拿来当分类变量用——这正是把 round 函数用出花的同一类思路工具本身简单值钱的是把它接到什么场景上。2.4 产生新变量的四条路径数据步真正干活的地方是从已有变量算出新变量。常见路径有四条表达式或函数直接算、if-then 条件赋值、retain 累加、do 循环批量生成。data score3; set score2; length level $6; total math english; if total 180 then level A; else if total 150 then level B; else level C; run;length level $6必须在赋值之前声明否则 SAS 按第一次出现的值长度定长后面写入更长的字符串会被截断且日志里只有一条容易被忽略的提示。if-then 链要注意 else 的配对条件从严格到宽松依次写顺序反了会把本该进 A 的记录提前分到 B。retain 用于跨观测保留值做累计和、累计次数时是唯一正解data cum; set score3; retain sum_score 0; sum_score sum_score total; run;retain 让 sum_score 在 data 步的每次迭代之间不被重置不写 retain每读一行它都会变回缺失值。替代写法是sum_score total;这个累加语句它自带 retain并且遇到缺失值自动跳过而不是把整个和变成缺失——这一点比手写加号更稳。do 循环则适合生成规律性的新变量或做重复计算循环变量要配合 output 语句控制输出时机。3. 函数与清洗从会用到用对的分水岭函数在 SAS 里是最容易被低估的部分。帮助文档能查到的语法是最不值钱的信息真正拉开差距的是知道某个函数在什么业务场景下能顶替一整套流程。数据清洗同理合并、查重、缺失、异常这四道工序写得出程序的人和写对程序的人中间隔着一层对机制的误解。3.1 函数按用途分四类先记住返回值SAS 函数数量庞大按返回值归类去记比按字母顺序背有效得多数值计算类round、int、abs、sum、mean、字符处理类substr、scan、compress、strip、upcase、index、length、日期时间类today、intck、intnx、year、month、datdif、类型转换类input、put。另有概率分布类和 dif/lag 这类特殊用途函数。round 是最典型的例子。只知道它四舍五入那它就是个基础函数但如果用它配合一个除数把连续变量切成组它就变成分组工具grp round(score, 10);把分数按 10 分一档归并比写一长串 if-then 干净得多。count 函数同理统计某段文本出现的次数可以用来分析商品评价里关键词的密度——函数本身一行背后是一个分析思路。字符函数里最常用的是 substr 和 scansubstr 按位置截取scan 按分隔符取第 n 段。要留意 substr 在 SAS 里既可以取值也可以赋值写在等号左边时会直接改原变量长度不匹配时补空格。data clean; set raw; city scan(addr, 1, ); code substr(id, 1, 4); if index(email, ) 0 then email_ok 0; else email_ok 1; run;scan 的第三个参数是分隔符集合 表示按空格切index 返回子串首次出现的位置找不到返回 0用这个特性做格式校验比正则更轻。要注意 scan 对连续分隔符的处理和 substr 越界时的返回值不同前者跳过空段后者返回空字符串。日期函数里 intck 算两个日期之间隔了几个时间单位intnx 按单位推移日期两者配合可以生成上月末本年初这类报表常用的时间点。日期本质是 1960 年 1 月 1 日起算的天数所以日期减日期得到的是天数这一点想通了很多日期怎么减出个奇怪数字的困惑就消失了。3.2 dif 与 lag 的取值机制dif 和 lag 是 SAS 函数里最容易用错的因为它们看起来像取上一行的值实际行为取决于它们在 data 步里被调用的次数和位置。lag(x) 返回的是上一次执行到这一句时的 x 值而不是上一行观测的 x 值。data lag_demo; set sales; last_amt lag(amount); if month 3 then lag3 lag(amount); diff_amt dif(amount); run;这段里 last_amt 是上一行的 amount因为每行都会执行一次。但 lag3 只在 month3 时执行所以它返回的上一次是上一个 month3 的观测不是上一行。如果把它写在 if 里面又放在条件判断之后行为会更绕。稳妥做法是把 lag 放在 data 步最外层、无条件执行需要条件取值时先算出全量再筛选。dif(x) 等价于 x - lag(x)同样有这个陷阱。注意lag 不是读上一行是读上一次调用。凡是把 lag 或 dif 写进 if 块、写进 do 循环内部的写法都要先在纸上把执行路径走一遍。3.3 set 纵向合与 merge 横向合的关键差异数据合并只有两个动作set 把多张表上下摞起来merge 把多张表按关键变量并排接起来。set 要求变量名一致、类型一致变量不完全相同时 SAS 会取并集缺失的位置补空值。merge 则必须先用 proc sort 按 by 变量排序未排序直接 merge日志里会出现一条警告结果却照样输出这才是最坑的地方——结果看着正常实际匹配全错。proc sort dataorders; by id; run; proc sort datacustomer; by id; run; data merged; merge orders(ino) customer(inc); by id; if o; run;in 选项生成临时指示变量值为 1 表示该观测来自对应数据集。if o;的意思是只保留订单表里存在的记录等价于左连接if o and c;是内连接if c;反过来是右连接。没有 in 选项时merge 会把两边所有 id 都保留一侧缺失的变量填空很容易在后续统计里把没有交易记录的客户算进分母。一对多匹配是另一个易错场景订单表一个 id 有多行客户表一个 id 一行merge 之后客户信息会被复制到每一行订单上这是正常行为但如果两张表都是一对多结果会按笛卡尔方式膨胀行数暴增而日志不报错。3.4 查重与 first. / last. 的配合查重最直接的办法是排序时加 nodupkeyproc sort dataraw outuniq nodupkey dupoutdups; by id; run;nodupkey 按 by 变量判重重复的观测被踢到 dupout 指定的数据集里这样既清了重又留下了被清掉的是什么方便核对。只想按部分变量判重、又不想丢其他变量时就不能用 nodupkey得靠 first. 和 last.。proc sort dataraw; by id date; run; data latest; set raw; by id; if last.id; run;by 语句会生成 first.id 和 last.id 两个自动变量分别标记每个 id 组的第一行和最后一行。if last.id;保留每组最后一条配合 by 里的 date 排序就是每个 id 取最新一条记录。这个写法在实际业务里出现频率极高比 proc sql 的 group by 更轻。要记住 first./last. 依赖排序by 变量顺序决定了第一和最后是按什么定义的。3.5 缺失值与异常值数组和 if/where 的分工查找缺失值变量少时写 if 就行变量上百个时只能靠数组。array nv[*] _numeric_;会把所有数值型变量装进数组_character_对应所有字符型变量dim() 返回元素个数missing() 判断是否缺失。data miss; set raw; array nv[*] _numeric_; array cv[*] _character_; do i 1 to dim(nv); if missing(nv[i]) then do; vname vname(nv[i]); output; end; end; keep id vname; run;外层循环遍历数值变量vname() 函数取出变量名output 输出一条哪个 id 的哪个变量缺了的记录。这段程序改成宽表输出就是一个通用的缺失值清单换个数据集直接能跑属于可以存进个人代码库的那类东西。字符型变量同理把 nv 换成 cv 即可。异常值排查和缺失值思路一致但要先分清 if 和 where。where 作用于读入阶段符合条件的数据才会进入 PDV因此效率更高且不能引用 data 步里新产生的变量if 在 PDV 上判断可以引用任何已存在的变量但数据集已经全量读进来了。写子集时用 where 更快写需要用到新变量的条件时只能用 if。这个区别在数据量大时会直接影响运行时间。data subset; set raw; where score 100; /* 读入阶段过滤不能引用新变量 */ flag (score 120); /* 新变量只能用 if 判断 */ if flag; run;缺失值填补是清洗的最后一步常见做法是用均值、中位数或前一条有效值填充。前一条有效值填充可以用 retain 配合 if 实现但要注意第一条观测就缺失时无法继承需要预置初值。4. 易漏点SAS 新手最容易踩空的五个地方这一章单独拎出来是因为下面这些点在教材里通常只用一句话带过实际却能把人卡上大半天。每一条按错在哪、为什么容易错、怎么补的顺序说都是可以在写程序前先自查一遍的。4.1 分号的位置数据行的分号必须另起一行最容易漏的是 cards 之后那段数据结尾的分号。很多人习惯把最后一条观测和分号写在同一行0 24;看起来毫无问题实际上 SAS 会把分号连同前面的数据一起当成数据内容读进去或者把下一句语句吞掉报错信息还指向别处。为什么会漏因为其他所有 SAS 语句的分号都紧跟在语句末尾唯独数据结束符要求独占一行规则不统一肌肉记忆会骗人。怎么补写完 cards 段落后养成习惯——数据全部写完后回车单独一行写分号再写 run。日志里出现数据行上的无效数据或莫名其妙的语法错误时第一件事就是回去看这个分号。4.2 忘了 libnameWork 库关掉就清空辛苦建好的数据集第二天打开 SAS 找不到八成是建在了 Work 里。Work 是临时库SAS 一旦正常退出就自动清空没有任何提示也没有回收站。为什么会漏因为建库时 SAS 不报错、不警告当天用着一切正常问题在下次启动才暴露。怎么补在程序最上方固定写一段 libname所有 data 语句都用库名.数据集名的完整写法如果嫌每次手写麻烦把 libname 放进自动执行的启动程序里。判断一个数据集是不是永久的看它有没有库名前缀就够了。4.3 if 和 where 混着用where 和 if 都能筛数据但作用阶段不同。where 在读入阶段过滤不能引用 data 步里新建的变量if 在 PDV 上判断什么都能引用但数据已经全读进来了。把 where 写在新变量后面SAS 直接报变量未找到把 if 当成 where 用大数据集上会白白多读一遍。为什么会漏两者的表面效果一模一样输出结果看不出差别只有报错或跑得慢的时候才暴露。怎么补记住一条判断标准——条件里只出现原数据集变量的优先用 where条件里出现 data 步新算出来的变量的只能用 if。写子集时先想清楚这一点能省下不少试错。4.4 merge 之前没排序或者忘了 inmerge 要求两张表都按 by 变量排好序。没排序时 SAS 在日志里给一条警告然后照样输出结果而结果基本是错的——匹配上的组合和预期完全不同。另一个版本是排了序但没加 in一侧没有匹配的记录被保留下来后续算平均值时分母悄悄变大。为什么会漏因为警告不是错误程序照常跑完结果表看着也有模有样。怎么补凡是写 merge前面必然跟两个 proc sort这是不需要思考的固定动作然后根据业务含义决定加不加 in左连接、内连接、右连接分别对应if a;、if a and b;、if b;。养成 merge 后立刻proc freq看一眼行数的习惯。4.5 字符型数字与数值型的隐式转换0012 和 12 在 SAS 里是两个东西前者是字符串后者是数值。用字符型 id 去 merge 数值型 idSAS 会自动转换日志里给一条字符值已被转换为数值的提示然后匹配结果里莫名其妙少了一批记录——因为 0012 转成数值变成 12前导零丢了原本能对上的对不上了。为什么会漏自动转换不报错只在日志里留一行提示而多数人跑完程序只看输出窗口。怎么补merge 前用 proc contents 核对两边的变量类型不一致就统一——数值转字符用 put字符转数值用 input别指望 SAS 自动处理。同样的问题也出现在日期上日期是数值按字符读进来就成了一串无意义的数字。5. 绘图与制表sgplot、tabulate、ODS 的输出路线图SAS 的图形和报表能力在 9.3 之后补得很足但从能出图到出对图之间有几个明确的台阶知道哪类图用菜单点、哪类图必须写 proc、报表怎么排成标准三线表、结果怎么直接落到 Word 里。这一章按从图到表的顺序把这几个台阶标出来。5.1 菜单出图与 sgplot 的分工SAS 的菜单里内置了一批常用图形分布图、箱式图、散点图加回归线、序列图、带状图以及金字塔图这类比较特殊的图形。数据探索阶段用菜单点几下就能看到大致形态速度比写程序快。但菜单出图的问题是不可复现——换一批数据要重新点一遍图形细节也调不细。proc sgplot 补的正是这一块。它把图形写成代码参数化之后换数据直接重跑样式也能精确控制。proc sgplot datasales; vbar month / responseamount statsum datalabel; xaxis label月份; yaxis label销售额; run;vbar 画柱状图response 指定要汇总的数值变量statsum 表示按 sum 统计默认是频数datalabel 在柱子上标出数值。xaxis 和 yaxis 用来改轴标签不加的话轴名直接显示变量名交出去的图表会显得很粗糙。双坐标轴是 sgplot 的高频需求比如柱形表示销售额、折线表示增长率proc sgplot datats; vbar month / responseamount; series xmonth yrate / y2axis; yaxis label销售额; y2axis label增长率; run;关键在于第二个图形语句加y2axis选项它把这组数据挂到右侧纵轴上两个 yaxis 语句分别控制左右轴的标签和刻度。要注意两组数据的量级差异太大时右轴刻度需要手动指定否则折线会被压成一条直线。除常规图之外sgplot 还能拼出柱状图的多种变体、瀑布图、气泡图、风险图和森林图这些在别的软件里往往要单独找包SAS 里是同一个 proc 换选项。5.2 proc template 与 gradar、gmap 能补什么sgplot 的图形类型是预设好的想彻底改样式——比如做一个配色和网格都自定义的饼图、马赛克图、截断坐标轴图、三维直方图、热图——就得动 proc template。template 定义的是图形模板改一次模板所有引用它的图统一变样适合要给一套报表统一视觉规范的场景。proc gradar 负责雷达图和日历图这两类图形 sgplot 做不了。雷达图常用于多指标对比日历图适合展示按日期分布的密集事件。proc gmap 则是地图专用配合 SAS 自带的地图数据集可以画全国图、省图用 shape 文件还能画更细的地区图。proc gmap datastat mapmaps.china; id id; choro amount / discrete; run;map 指定地图数据集data 里的 id 要和地图里的 id 对应choro 按数值填色discrete 表示按离散值分色而不是连续渐变。地图这块最容易卡住的是 id 对不上——两边的编码体系不一致时图能出来但一片空白需要先 proc contents 核对。5.3 tabulate 与 report从统计表到三线表制表的需求分两层一层是把统计算出成表另一层是把表修饰成能直接交出去的样子。proc tabulate 擅长前者它的语法用逗号分隔行、列和表页三个维度。proc tabulate datacls; class gender; var score; table gender, score*(n mean std); run;class 声明分类变量var 声明分析变量table 语句里逗号前是行维度、逗号后是列维度score*(n mean std)表示对 score 同时算例数、均值、标准差。想要三维列联表就再加一个逗号星号表示嵌套或交叉。tabulate 的修饰能力很强可以改表头文字、加合计行、控制小数位。标准三线表是很多人卡住的地方——tabulate 默认输出带完整网格线直接贴进报告不合格。做法是配合 ODS style 控制边框或者用 proc template 定义表样式把横线只留在表头和表尾。这一步看着琐碎但它是能出表和能交表的分界线。proc report 更偏向明细报表可以按组分列显示统计量也可以列表显示符合条件的观测proc report datacls nowd; column gender score; define gender / group 性别; define score / mean 平均分 format8.2; run;column 决定列的排列顺序define 逐个说明用法group 表示按该变量分组mean 表示取均值后面的单引号内容替换表头文字format 控制显示格式。nowd 去掉默认的交互窗口让结果直接进输出。5.4 ODS 把结果送到 Word、Excel 和 PDF前面所有 proc 的输出默认落在结果窗口要变成文件得靠 ODS。它的用法是开一个目标、跑程序、关掉目标三明治结构。ods rtf filereport.rtf stylejournal; proc tabulate datacls; class gender; var score; table gender, score*(n mean std); run; ods rtf close;ods rtf 打开 RTF 目标并指定文件名stylejournal 是一套偏学术的样式边框克制、接近三线表效果做报告时很实用。中间跑的所有 proc 输出都会被写进这个文件最后 ods rtf close 收尾——漏写 close 会导致文件被占用、打不开这是最常见的翻车点。把 rtf 换成 pdf 或 excel 就是另外两种格式语句结构完全一致。ODS 还可以配合 proc template 定义表格模板让同一套表在多份报告里保持一致的列宽和对齐方式。提示ods 目标打开后如果不 close后续所有输出都会继续往里追加文件句柄也一直占着。写 ods 语句时把 close 和打开成对写中间再填内容。6. 复习清单把 SAS 基础过一遍的自查方式复习 SAS 基础最没效率的方式是从第一页翻到最后一页。有效的方式是拿一张清单逐条问自己能不能在不看资料的情况下写出对应的程序写不出来的那一条才是要补的地方。下面这张表是我自己反复用的自查框架按能不能默写分三档每一档对应不同的处理动作。模块自查条目达标标准不达标怎么办数据步data、input、cards 三句话结构能默写含字符、数值、日期三种类型的建库程序回看分号规则和 informat 写法逻辑库libname 与永久数据集能说清 Work 与永久库的区别并写出 libname检查是否有程序还全建在 Work 下格式informat 与 format 的分工能各写一个例子并说明生效时机重点区分读入时和显示时新变量表达式、if-then、retain、do 循环能说明四种方式分别适合什么场景手写一个累计求和验证 retain函数数值、字符、日期、类型转换四类每类能说出三个函数及典型用途从 round 和 scan 开始补合并set 与 merge 的差别能写出带 in 的左连接并解释结果行数检查 merge 前是否都排了序查重nodupkey 与 first./last.能写出每个 id 取最新一条确认 by 变量顺序的含义缺失数组遍历与 missing 函数能改写万能程序适配新数据集先跑通单变量的缺失统计绘图sgplot 常规图与双坐标轴能画出带标签的柱状图和双轴图检查 y2axis 挂在哪条语句上制表tabulate 的行列维度能写出带 n、mean、std 的汇总表用 ODS style 调三线表外观清单之外还有三个动作值得固定下来。第一个是每次跑完程序必须看日志不是扫一眼有没有 ERROR而是看 WARNING 和 NOTE——字符转数值、变量被截断、merge 未排序这些提示全在 NOTE 里忽略了就等于把错误带进结果。第二个是任何 merge 之前无脑加两个 proc sort不给自己判断这张表应该是有序的吧的机会。第三个是每写完一段清洗程序用 proc freq 或 proc means 核对一次行数和关键变量的分布确认数据是被处理了而不是被处理没了。我自己的教训很具体早期做数据合并两张表都没排序就直接 merge日志里那条警告被我一划而过结果表跑出来一千多行我还以为是数据本身就这么大直到交付前被问为什么客户数比订单数还多才发现问题。从那以后我每次写 merge 都强制走一遍先 sort、再 merge、后核行数这三步一步不落。这套动作看着笨但它替我挡掉的返工远比多敲的几行代码值钱。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?