Stata 数据处理系列:日期与时间数据

2022-08-13 21:53:02 浏览数 (1)

日期(Date)与时间(Time)(后文统称为“时期数据”)在时间序列与面板数据分析中经常出现,在 Stata 中掌握处理这类数据的函数很有必要。

1. 时期数据处理概览

Stata 提供了许多用于处理日期和时间数据的函数,这些函数能够帮助我们在字符和数值之间进行转换,以及将不同类型的时期数据进行彼此间的转换。相信大家都有这样的经历,当我们在 Excel 单元格中输入 “2022-07-20”后,单元格会自动转换成“2022年7月20日”,右键单击此单元格后可进一步选择 “设置单元格格式” ,并在众多格式选项中选择 “日期” 栏,最后选择按照我们的要求呈现出来的时期数据。此时 Excel 表格中的时期数据可被用于计算。

在 Stata 中对时期数据进行处理的逻辑与 Excel 相似,但通常将 Excel 数据导入Stata 后,导入的时期数据类型被识别为字符型(即便在 Excel 中是数值型的),而字符型数据是无法用于数据运算的。换言之,我们需要将导入 Stata 的字符型数据转换为可用于计算的数值型数据。不仅如此,还要能够按照我们偏好的格式将时期数据呈现出来。

2. 时期数据的格式

时期的呈现有多种形式(forms)。Stata 能够支持以下类型的时期数据:

时期类型

时期数据在 Stata 中的可读格式

代码设定格式

datetime

20jan2020 09:15:22.120

%tc

date

20jan2020, 20/01/2020, 2020.01.20,Jan. 20, 2020;其他

%td

weekly date

2020w3

%tw

monthly date

2020m1

%tm

quarterly date

2020q1

%tq

half-yearly date

2020h1

%th

yearly date

2020

%ty

需要注意的是,上表第二列所呈现的日期格式是我们在 Stata 中能够看到的,不同的格式对应着不同的数值,具体为距离1960年1月1日(01jan1960)的(正或负) 长度(positive or negative durations)。使用“mdy()”函数,我们可以获得指定日期与1960年1月1日的差距。下面的示例结果表明,1960年1月1日作为一个参照点(base date),该日与其自身(参照点)的差为 0,参照点之前为负整数,之后为正整数。例如,对于2020年1月20日这个日期,在 Stata 中其实是用 21934 这个数值表示的。同理,Stata 还提供了其他时期函数(Date and time functions)用于帮助我们获得其他日期数据对应的数值,这些数值对于开展某些研究而言可能是有用的,这里仅关注常规的时期数据(即“年-月-日”)。

代码语言:javascript复制
di mdy(12,31,1959)
* -1

di mdy(1,1,1960)
* 0

di mdy(1,3,1960)
* 2

di mdy(1,20,2020)
* 21934

上表第三列的代码设定格式可将 Stata 规则下日期对应的数值进行展示。所有设定格式均以 %t 开头;第二个字母表示时期数据的类型,例如: %td,表示日度日期(daily dates);%tw 表示周日期(weekly dates)。下面的例子展现了如何将字符型变量 “date” 转化成对应的 Stata 规则下的数值型变量,以及如何将转化后的数值型变量的呈现格式进行设定。

代码语言:javascript复制
clear all
input str30 date //生成单个观测点数据
"20/01/2020"
end

display mdy(1,20,2020)
*21934
generate date_numeric=date(date, "DMY") //将字符型时期数据进行数值转换,具体参见“时期数据的转换”
generate date_numeric2=date_numeric //生成同一个变量,用于比较格式化呈现前后的效果
format date_numeric2 %td
browse

3. 时期数据的转换

导入 Stata 的时期数据通常被视为字符型数据,我们首先要做的就是将字符型数据转换成数值型数据。值得注意的是,不同于常规的数据类型转换操作,由于 Stata 对日期数据的存储有自己的规范,时期数据的数值化转换,其目的就是获得前文所示的距离参考时点的差值,这个唯一的差值可通过各类日期函数展开进一步处理。

具体看如下示例。该数据集中的时期数据均为字符型变量,以出生日期(dateofbirth)和入院日期(admit_d)的第一个观测值为例,分别为May152001 和 20110625。显然,我们无法针对字符型变量进行更多运算处理,例如排序或计算,这时便需要将其进行转化。

代码语言:javascript复制
use "https://www.stata-press.com/data/r17/visits", clear //本例来源于 Stata Manual  

describe
/* Contains data from https://www.stata-press.com/data/r17/visits.dta
 Observations:             5                  Fictional hospital visit data
    Variables:             7                  27 Aug 2020 22:56
-------------------------------------------------------------------------------------------------
Variable      Storage   Display    Value
    name         type    format    label      Variable label
-------------------------------------------------------------------------------------------------
patid           byte    %9.0g                 Patient ID
dateofbirth     str9    %9s                   Date of birth
reason          str15   s                  Reason for visit
admit_d         str8    %9s                   Admission date
admit_t         str17   s                  Admission date and time
discharge_d     str9    %9s                   Discharge date
discharge_t     str14   s                  Discharge date and time
-------------------------------------------------------------------------------------------------
Sorted by: */

list admit_d dateofbirth
  /*
      ---------------------- 
     |  admit_d   dateofb~h |
     |----------------------|
  1. | 20110625   May152001 |
  2. | 20110313   Apr011999 |
  3. | 20110409   Nov151975 |
  4. | 20120211   Aug261960 |
  5. | 20120801   Dec161987 |
      ----------------------    */

利用 “date()” 函数(又称“字符-数值转换函数”),可以将字符型的时期变量转换为 Stata 的时期数值(可以称其为:Stata dates)。例如,上面提到的第一个观测值的 “20110625” 和 “May152001” ,分别应对着数值 18803 和 15100,既以日为单位的与参照时间点(1960年1月1日相隔的日期数量)的差值。基于此数值,可以进一步将其设定为我们需要的日期格式。这里需要注意,载入日期数据后,需要按照排列规则进行设定,这里即为 “year, month, day (YMD)”和“month, day, year (MDY)”。

代码语言:javascript复制
generate admit = date(admit_d, "YMD")  //20110625 - YMD
generate dob = date(dateofbirth, "MDY") //May152001 - MDY
  /*注:It does not matter whether the month is written as a number, spelled out completely, or abbreviated to three letters.*/
list admit_d admit dateofbirth dob 
  /*
      -------------------------------------- 
     |  admit_d   admit   dateofb~h     dob |
     |--------------------------------------|
  1. | 20110625   18803   May152001   15110 |
  2. | 20110313   18699   Apr011999   14335 |
  3. | 20110409   18726   Nov151975    5797 |
  4. | 20120211   19034   Aug261960     238 |
  5. | 20120801   19206   Dec161987   10211 |
      --------------------------------------   */

紧接着,我们将 Stata 规则下数值型变量的呈现格式进行调整:

代码语言:javascript复制
format admit dob %td //将 Stata 规则下的数值型日期变量进行格式调整
list admit_d admit dateofbirth dob //格式调整后即为我们可以阅读理解的呈现形式
  /*
      ---------------------------------------------- 
     |  admit_d       admit   dateofb~h         dob |
     |----------------------------------------------|
  1. | 20110625   25jun2011   May152001   15may2001 |
  2. | 20110313   13mar2011   Apr011999   01apr1999 |
  3. | 20110409   09apr2011   Nov151975   15nov1975 |
  4. | 20120211   11feb2012   Aug261960   26aug1960 |
  5. | 20120801   01aug2012   Dec161987   16dec1987 |
      ----------------------------------------------   */

des admit_d admit dateofbirth dob     
  /*
     Variable      Storage   Display    Value
         name         type    format    label      Variable label
     ----------------------------------------------------------------
     admit_d         str8    %9s                   Admission date
     admit           float   %td                   
     dateofbirth     str9    %9s                   Date of birth
     dob             float   %td   */                

上面的结果也再次表明,即便原始数据均为数字,它依然可以是字符型的变量;即便变量中有字母出现,它仍可表示为一个数值。因此,不畏浮云遮望眼,理解 Stata 规则下的时期数据转化规则至关重要。只要搞清楚时期数据的处理逻辑,使用合适的时期函数自然也不再困难,上面对于日度日期数据(daily dates)的处理也完全适用于周度(weekly)、月度(monthly)和季度(quarterly)数据。否则,我们将面对汗牛充栋的时间函数命令,看似学了很多命令,但却在耗费大量时间和精力后依然混乱不堪。

4. 时期数据的提取与合并

对于 Stata 规则下的数值型变量,我们还可以通过函数对其内容进行提取。以上文中已经转换为 Stata Dates 的变量admit为例,使用monthyear 函数,可以提取该变量中的时间成分,即月和年。

代码语言:javascript复制
generate admonth = month(admit) ///提取月
generate adyear = year(admit) ///提取年

list admit admonth adyear
  /*
      ------------------------------ 
     |     admit   admonth   adyear |
     |------------------------------|
  1. | 25jun2011         6     2011 |
  2. | 13mar2011         3     2011 |
  3. | 09apr2011         4     2011 |
  4. | 11feb2012         2     2012 |
  5. | 01aug2012         8     2012 |
      ------------------------------  */

反之,也可以将两个时间变量进行合并。使用ym() 函数,可以将分开的日期元素进行合并。例如,将上面提取的年、月进行合并:

代码语言:javascript复制
generate monthly = ym(adyear,admonth)
format monthly %tm

list admit monthly
 /*
      --------------------- 
     |     admit   monthly |
     |---------------------|
  1. | 25jun2011    2011m6 |
  2. | 13mar2011    2011m3 |
  3. | 09apr2011    2011m4 |
  4. | 11feb2012    2012m2 |
  5. | 01aug2012    2012m8 |
      ---------------------  */

此外,还能以嵌套函数的方式进行合并:

代码语言:javascript复制
generate monthly1 = ym(year(admit), month(admit))
format monthly1 %tm
list admit monthly monthly1
 /*
      -------------------------------- 
     |     admit   monthly   monthly1 |
     |--------------------------------|
  1. | 25jun2011    2011m6     2011m6 |
  2. | 13mar2011    2011m3     2011m3 |
  3. | 09apr2011    2011m4     2011m4 |
  4. | 11feb2012    2012m2     2012m2 |
  5. | 01aug2012    2012m8     2012m8 |
      --------------------------------  */

5. 不同时间类型之间的转换

首先,将完整的时期数据转换为日期数据:

代码语言:javascript复制
generate double admit_time = clock(admit_t, "YMDhms") //将 datetime 从字符型转换为 Stata Dates
format admit_time %tc //呈现格式设定
list admit_time admit_t

generate dateoftime = dofc(admit_time) //将 datetime 直接转换为 date,亦可理解为一种特殊的提取
format dateoftime %td
list admit_time dateoftime
 /*
      -------------------------------- 
     |         admit_time   dateoft~e |
     |--------------------------------|
  1. | 25jun2011 05:15:06   25jun2011 |
  2. | 13mar2011 08:30:45   13mar2011 |
  3. | 09apr2011 10:17:08   09apr2011 |
  4. | 11feb2012 10:30:12   11feb2012 |
  5. | 01aug2012 06:45:59   01aug2012 |
      --------------------------------  */

其次,将日期数据转换为月度数据:

代码语言:javascript复制
generate monthofdate = mofd(admit)
format monthofdate %tm
list admit monthofdate
 /*
      ---------------------- 
     |     admit   montho~e |
     |----------------------|
  1. | 25jun2011     2011m6 |
  2. | 13mar2011     2011m3 |
  3. | 09apr2011     2011m4 |
  4. | 11feb2012     2012m2 |
  5. | 01aug2012     2012m8 |
      ----------------------  */

同理,还可以将月度数据转换为季度数据:

代码语言:javascript复制
generate quarterly1 = qofd(admit)
generate quarterly = qofd(dofm(monthofdate)) //注意:使用嵌套函数的原因在于,没有直接从月数据转换为季度数据的函数  
format quarterly quarterly1 %tq
list monthofdate quarterly quarterly1
 /*
      -------------------------------- 
     | montho~e   quarte~y   quarte~1 |
     |--------------------------------|
  1. |   2011m6     2011q2     2011q2 |
  2. |   2011m3     2011q1     2011q1 |
  3. |   2011m4     2011q2     2011q2 |
  4. |   2012m2     2012q1     2012q1 |
  5. |   2012m8     2012q3     2012q3 |
      --------------------------------  */

此外,td() 函数可以将单个指定的日期转换为 Stata Dates 对应的数值,进而用于表达式中:

代码语言:javascript复制
list admit patid reason if admit > td(20feb2012) //2012年2月20日后患者的入院原因   
 /*
      ------------------------------------- 
     |     admit   patid            reason |
     |-------------------------------------|
  5. | 01aug2012       5   rapid breathing |
      -------------------------------------  */
     
dis mdy(2,20,2012) //指定日期对应的 Stata Dates 数值
19043

以上内容就是 Stata 处理日期与时间数据的基础内容。时期数据的处理逻辑十分清晰,即:先将字符转数值、再将数值格式化、按照需求来转换。

接下来,我们进一步结合实例,不仅将时期数据与画图相结合,也将介绍一些实用的函数帮助我们获取更多基于时期数据的变量信息。


版本信息:

  • 第一版:2022年8月13日

0 人点赞