SAS 入门必读:深度解析日期的输入格式与显示格式

在处理数据分析任务时,日期往往是最棘手的数据类型之一。你是否曾在 SAS 中尝试读取日期,结果却得到一串毫无意义的数字?或者明明导入了日期数据,却无法按时间顺序正确排序?

别担心,在这篇文章中,我们将深入探讨 SAS 编程中至关重要的两个概念:Informats(输入格式)Formats(显示格式)。我们将不仅学习它们是什么,还会通过实战代码示例,掌握如何精准地控制日期数据的读取与展示。通过这篇文章,你将学会如何驯服 SAS 中的日期,让它们乖乖听话。

什么是 Informats 和 Formats?

在开始写代码之前,我们首先需要厘清两个经常被混淆的核心概念。简单来说,它们决定了 SAS 如何"看"数据和"秀"数据。

Informats(输入格式):数据的翻译官

输入格式 用于告诉 SAS 如何读取数据。这就好比当我们阅读一份英文文档时,我们需要先理解单词的含义。

当我们使用 INLINECODEca43bf96/INLINECODEffd8ae28 语句直接在代码中输入数据,或者从外部的 Text、Excel、CSV 文件读取数据时,SAS 看到的只是一串字符(例如 "2023-10-01")。如果没有正确的指示,SAS 可能会把它当成一串普通文本,或者读错。这时候,我们就需要 Informat 来告诉 SAS:“嘿,把这串字符按照‘日-月-年’的顺序转换成内部的日期数值。”

Formats(格式):数据的化妆师

格式 则用于告诉 SAS 如何显示或写入变量的值。这是关于“呈现”的规则。

SAS 内部存储日期时,实际上存储的是从 1960年1月1日 到该日期的天数(这在后文会详细解释)。如果你直接看这个数字,比如 21915,你是看不出它是哪一天的。Format 的作用就是把 21915 这个“素颜”数字,化妆成人类可读的“2020-01-01”或其他你喜欢的样子。

使用范围的区别

  • 输入格式:主要在读取数据时使用,因此几乎只在 Data Step(数据步) 中配合 INPUT 函数或语句使用。
  • 格式:既可以在 Data Step 中定义显示样式,也可以在 PROC Step(过程步)(如 PROC PRINT)中使用,控制报表输出的样子。

SAS 日期的底层逻辑:神秘的数字

在深入代码之前,我们需要了解一个关键机制:SAS 存储日期的本质是数值

SAS 将 1960年1月1日 定义为内部坐标的零点(0)。

  • 1960年1月2日 存储为 1。
  • 1959年12月31日 存储为 -1。
  • 今天的日期,比如 2023年10月1日,存储为 23399 左右(具体取决于时区设置,但你可以想象这是一个巨大的整数)。

这意味着,如果你没有指定显示格式,SAS 就会直接把这个巨大的整数扔给你。这就是为什么很多人新手看到结果时会一脸懵:“我要的是日期,怎么给我一堆乱码?”

实战演练 1:读取不同格式的日期字符串

让我们来看一个实际的例子。在这个例子中,我们有两列不同格式的日期数据。我们的任务是让 SAS 正确地识别它们。

场景描述

假设我们有一行数据:

20-07-19 20-07-2019

第一部分是 INLINECODE505f1999 格式(8个字符宽度),第二部分是 INLINECODE4eaf3fa4 格式(10个字符宽度)。注意:这里的“8”和“10”指的是读取宽度,SAS 会从指针所在位置开始数这么多位。

代码示例

/* 创建一个名为 sampledata 的数据集 */
DATA sampledata;
    /* 
       INPUT 语句用于指定读取规则:
       @6  : 将指针移动到第 6 列。
       date1 : 第一个变量名。
       ddmmyy8. : 告诉 SAS 读取接下来的 8 个字符,并将其解析为“日-月-年”格式的日期。
       
       @15  : 将指针移动到第 15 列(跳过中间的空格和已读区域)。
       date2 : 第二个变量名。
       ddmmyy10. : 告诉 SAS 读取接下来的 10 个字符。
    */
    INPUT @6 date1 ddmmyy8. @15 date2 ddmmyy10.;
    
    /* 紧接着是 CARDS/DATALINES 数据块 */
    CARDS;
20-07-19 20-07-2019
;
RUN;

结果解析

如果你此时运行 PROC PRINT; RUN;,你会看到令人惊讶的结果:

date1    date2
21750    21750

虽然我们读取的是 2019年7月20日,但显示出来的却是 21750。正如前文所述,这就是 SAS 的内部存储值。如果我们不告诉 SAS 如何“化妆”,它就只会展示素颜。

实战演练 2:使用 FORMAT 语句美化输出

为了让上面的数字变成我们熟悉的日期格式,我们需要引入 FORMAT 语句。这是解决“数字乱码”问题的金钥匙。

DATA sampledata_formatted;
    /* 1. 读取数据(这一步和之前一样,把字符变成内部数字) */
    INPUT @6 date1 ddmmyy8. @15 date2 ddmmyy10.;
    
    /* 2. 定义显示格式(这一步告诉 SAS 输出时如何把数字变回字符串) */
    /* 注意:这里使用的格式名称可以和输入时不一样,非常灵活 */
    FORMAT date1 ddmmyy8. date2 ddmmyy10.;
    
    CARDS;
20-07-19 20-07-2019
;
RUN;

/* 打印结果查看效果 */
PROC PRINT DATA=sampledata_formatted;
RUN;

输出结果:

date1     date2   
20-07-19  20-07-2019

你看,现在就清晰多了!这里的关键点在于:FORMAT 语句并没有改变存储在数据集中的数值(依然是 21750),它只是改变了它在报表中的显示方式。

实战演练 3:处理带有月份缩写的日期

在很多国际化的数据文件中,日期往往是 INLINECODEfd61fb64 或 INLINECODE88f42ad9 的格式,例如 20-jul-19。这种格式非常常见,因为它不会产生歧义(不像 01-02-03,谁知道是哪年哪月?)。

对于这种格式,我们可以使用 SAS 通用的 INLINECODE4f8548b3 格式(通常使用 INLINECODE35caf413 或 date11.)。注意,月份的缩写(如 Jul, Aug)必须是英文的前三个字母,且大小写通常不敏感。

代码示例

DATA temp;
    /* 
       @6: 指针跳到第6列
       dt: 变量名
       date11.: 读取11位宽度的日期,适用于 ‘20-jul-2019‘
    */
    INPUT @6 dt date11.;
    
    /* 我们也可以直接输出为不同格式,比如将其显示为 worddate (英文日期全拼) */
    FORMAT dt date11.; 
    
    CARDS;
20-jul-19
;
RUN;

/* 使用 noobs 选项可以隐藏观测号,让输出更简洁 */
PROC PRINT DATA=temp NOOBS;
    TITLE "读取 DD-MMM-YY 格式日期示例";
RUN;

输出结果:

dt         
20-jul-2019

代码深度解析:

在这个例子中,INLINECODEd9c64f61 输入格式非常智能,它能识别出连字符 INLINECODE719d6ac5 作为分隔符,并正确处理月份的英文缩写 INLINECODE1e573a5c。如果你尝试把 INLINECODE786ae171 改成中文的 INLINECODE9ce6c882 或数字 INLINECODEdc44b71f,SAS 可能会报错或读入缺失值。这就是输入格式严格性的体现。

深入理解:为什么我的数据读入了缺失值?

在实际工作中,最让人头疼的问题莫过于读到一堆点(.),这在 SAS 中代表缺失值。让我们看看几个常见的坑和解决方案。

案例 1:分隔符与格式不匹配

如果你的数据是 INLINECODE4d155dd3(用斜杠分隔),但你使用了 INLINECODE9a834ede 格式。INLINECODEa5b0fb3e 默认期望的是短横线 INLINECODE19573e25 或者无分隔符的紧凑数字(如 20072019)。

解决方案:使用 INLINECODEc41aefe6 格式系列时,SAS 通常能智能处理多种分隔符(如 /, -),但在某些特定格式下,必须明确告知。最安全的方法是确保源数据的一致性,或者使用 INLINECODE00c5c379 这种“智能猜测”格式。

案例 2:年份缺失导致的数据错误

如果你使用 INLINECODEe20d2e9d 读取 INLINECODEe6980b6f,SAS 会将其识别为 INLINECODE5a55b55f。但如果你读取的是 INLINECODEfe786053,SAS 会默认认为这是 INLINECODE1b40b591。这通常符合预期。然而,如果你的数据全是 1920 年代的出生日期(例如 INLINECODE4362d43e),SAS 可能会将其误判为 2029年

解决方案:SAS 有一个系统选项 INLINECODE7973a3cd,默认值通常是 INLINECODEa9effbfd。这意味着 26-99 被视为 1926-1999,而 00-25 被视为 2000-2025。

OPTIONS YEARCUTOFF=1920; /* 我们可以根据实际情况调整这个截断年份 */

最佳实践与性能优化建议

作为一名专业的 SAS 开发者,我们不仅要写出能跑的代码,还要写出健壮、易读的代码。

1. 永远指定宽度,但要留有余地

在使用 INLINECODE8160e633 或类似的格式时,数字(8, 10, 11)代表读取的列宽。如果你的数据中有不可见的空格,或者某些行是 INLINECODEeb4413d9(单月份),而你的格式是 ddmmyy8.,指针读取可能会错位。建议:尽量让源数据对齐,或者使用带分隔符的列表输入模式。

2. 区分 INPUT 函数与 PUT 函数(重要!)

这是很多新手最容易晕的地方。

  • INPUT 函数:把 字符数值/日期。当你有一列字符串 "20230101" 想变成日期变量时,用它。
  /* sas_date_val 是数值型 */
  sas_date_val = INPUT("20230101", yymmdd8.);
  • PUT 函数:把 数值/日期字符。当你想把日期转换成特定的字符串导出到文本文件时,用它。
  /* char_date 是字符型 */
  char_date = PUT(TODAY(), yymmdd10.);

记住:“I”nput converts Inward (to number), “P”ut converts P outward (to text)。

3. 利用 FORMAT 语句的持久性

一旦你在 Data Step 中使用了 FORMAT 语句,这个格式会跟随该变量传递到所有基于该数据集的 PROC 步骤中。这意味着你不需要在每次打印时都重新定义格式,这大大提高了效率并保持了输出的一致性。

更多实战场景:处理不规范的日期

让我们通过一个稍微复杂的例子来巩固我们的知识。假设我们遇到混杂的日期格式。

DATA messy_data;
    INPUT raw_date $20.; /* 先作为一个长字符串读取 */
    
    LENGTH clean_date 8;
    
    /* 使用 INPUTN 和 IF-THEN 逻辑来判断格式 */
    /* 如果包含连字符,尝试一种格式,否则尝试另一种 */
    IF INDEX(raw_date, ‘-‘) > 0 THEN DO;
        clean_date = INPUT(raw_date, ANYDTDTE10.);
    END;
    ELSE DO;
        clean_date = INPUT(raw_date, 8.); /* 尝试作为纯数字(SAS日期值) */
    END;
    
    /* 最后统一格式化输出 */
    FORMAT clean_date worddate20.; /* 显示为 July 20, 2019 这种样式 */
    
    DATALINES;
2023/10/01
21915
01-Oct-23
;
RUN;

PROC PRINT DATA=messy_data NOOBS;
    VAR raw_date clean_date;
RUN;

在这个例子中,我们先不分青红皂白地把日期当成字符读进来,然后再使用智能输入格式 INLINECODE11908c18 进行清洗。INLINECODEc7e09857 是一个非常强大的工具,当你不确定数据源的具体格式时,它能自动识别 INLINECODEe51f4e27、INLINECODE9eeec630、YYMMDD 等多种顺序。

总结与后续步骤

在这篇文章中,我们像工匠一样拆解了 SAS 处理日期的机制。从理解那串“神秘的数字”开始,到掌握 INLINECODEbce1554a 的读取指针,再到利用 INLINECODEf26dc665 为数据穿上外衣。

让我们回顾一下关键点:

  • 内部存储:SAS 日期本质上是数字(距离 1960年1月1日的天数)。
  • Informat (Input):用于 Data Step,解决“读进来”的问题,把字符变成数字。
  • Format (Display):用于任何地方,解决“秀出去”的问题,把数字变回字符显示。
  • 常见陷阱:注意年份的截断设置 (YEARCUTOFF) 以及分隔符的匹配。
  • 实战技巧:遇到复杂情况可先用字符读取,再用 INPUT 函数转换。

接下来的建议

既然你已经掌握了基础的格式化技巧,我建议你接下来尝试结合 INLINECODEa110b621 和 INLINECODE9bc3e404 函数 来学习如何进行日期的加减运算(例如:计算两个日期之间相隔多少个月,或者获取“下个月的第一天”)。这才是处理时间序列数据真正强大的地方。

希望这篇指南能帮助你不再畏惧 SAS 日期!试着去修改上面的代码,看看如果格式指定错误,SAS 会给你什么提示。最好的学习方式就是亲手去“破坏”它,再修好它。

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/19149.html
点赞
0.00 平均评分 (0% 分数) - 0