作者:Kreshna Gopal, Principal Computer Scientist and Software Developer
导读
本文的目的是描述Stata 18(2023年发布)中引入的两个新功能:1. framesets(框架集) 和 2. 别名变量 across frames。这些功能使Stata能够高效、方便地处理大量潜在的非常大的数据集。framesets允许您捆绑、保存文件并在内存中加载一组包含数据集的相关框架。Alias变量只需很少的内存就能允许您访问其他帧中的变量,就好像它们是当前帧的一部分一样。
Stata中的数据管理
1985年Stata 1.0发布时,数据以表格形式组织为观测值(行)和变量(列),称为数据集。数据集完全保存在内存中(然后以KB为单位),并作为.dta文件保存在磁盘上。数据类型,如整数、实数,尤其是字符串,都是经过节俭管理的。最初的44个命令大多用于数据管理,包括仍然不可避免的生成、替换和列表。这个底层框架仍然是随后的17个版本Stata的基石:数据集仍然作为表完全保存在内存中,使用强类型语言来处理数据。这使得Stata速度很快,并允许在几毫秒内处理数十亿次观测。然而,对于非常大的数据集,将整个数据集保存在内存中是有限制的。尽管如此,利用经济实惠的内存的惊人增长,Stata的数据管理功能不断变得更大、更强、更快。
在本文中,作者讨论了处理大型数据集的新功能,即帧、帧集和别名变量。作者将在接下来的三节中详细介绍这些功能。在本文末尾的附录中,作者概述了Stata的数据管理能力是如何随着时间的推移而增长的。
Frames:用于多个数据集的框架
对于庞大而复杂的数据,通常需要同时处理多个可能巨大的数据集。您可能想要多任务处理,并使用各种项目的各种数据集。或者,您可能正在使用一组相关的数据集,并希望整合它们之间的统计数据。有一些Stata命令,如 preserve 和 restore,使您能够从一个数据集切换到另一个数据集中。但这些都需要一些谨慎的编码,并且在将数据集保存到磁盘和从磁盘恢复数据集时会带来时间损失。
在Stata 16(2019)中,引入了一种用于处理多个数据集的新框架:frames。多个数据集可以在多个帧中保存在内存中。例如,以下是如何创建具有帧创建的帧,使该帧成为具有帧更改的当前(工作)帧,并将数据集加载到其中:
. frame create auto. frame change auto. sysuse auto(1978 automobile data)
可以复制框架并重命名框架:
. frame copy auto auto1. frame rename auto1 cars
数据集和包含它们的帧的名称可能不同。此外,即使内存中有多个帧,也可以一次交互处理一个帧(当前帧)。您可以使用pwf(打印工作框架)识别当前框架:
. pwf (current frame is auto)
默认情况下,始终使用当前帧。也就是说,帧前缀功能允许您在当前帧之外的帧上运行命令。例如,您可以在 frame cars 中生成一个新变量,例如 newvar(此处为随机值):
. frame cars: generate newvar = runiform()
也可以使用 frlink 在当前帧和另一帧之间创建链接。例如,可以通过匹配变量 make(保持汽车品牌)上的观察结果,在当前车架汽车和车架汽车之间创建一对一链接(通过指定1:1):
. frlink 1:1 make, frame(cars)(all observations in frame auto matched)
您可以通过 frame drop 来删除帧(如果不是当前帧):
. frame drop cars
您可以使用 reset frames:
. frames reset
这会将Stata重置为内存中只有一个空帧的状态。
你可以用框架做更多的事情:用 frame put 复制数据变量和观测,用frame post添加新的观测,等等。
请注意,与 frame 和 frames 相关的命令的工作方式与键入框架或框架完全相同;它们是同义词。
Stata最多支持100帧。就像单个数据集一样,所有帧都完全保存在内存中。这使得使用框架的速度也非常快。但它假设您可以在内存中拟合所有帧数据,这是Stata 18中两个新功能的驱动因素。
Stata 18新增功能:Framesets
Stata 18 添加了 frame 概念的自然演变:用户现在可以以高效内存的方式在磁盘上保存一组 frame 。为 framesets 引入了一种新的数据文件格式:.dtas,.dta的复数。
例如,让我们创建三个 frame ,并将三个不同的数据集(与预期寿命有关)加载到其中:
. frame create life0. frame create life1. frame create life2. frame life0: sysuse lifeexp(Life expectancy, 1998). frame life1: sysuse uslifeexp(U.S. life expectancy, 1900-1999). frame life2: sysuse uslifeexp2(U.S. life expectancy, 1900-1940)
您可以使用将这三个 frame 保存在一个帧集文件中,例如 life.dtas
. frames save life, frames(life0 life1 life2)file life.dtas saved
您可以稍后重置或清除所有 frame ,并使用加载 life.dtas 中保存的 frame
. frames reset. frames use life life0 68 x 6; Life expectancy, 1998 life1 100 x 10; U.S. life expectancy, 1900-1999 life2 41 x 2; U.S. life expectancy, 1900-1940
使用一组 frame 时,必须考虑许多因素。例如,如果要从磁盘加载的 frame 与内存中的 frame 名称相同,该怎么办?加载 frameset 时,哪个 frame 成为当前 frame ?如果您尝试加载一个以前链接的 frame ,但该 frame 已不存在,该怎么办?
作者提供了 frames-describe,它对内存和磁盘中的 frame 及其所包含的变量进行了评估。例如,下面给出了 frameset life.dtas 中 frame 的(简短)描述:
. frames describe using life, short-------------------------------------------------------------------------------Frame: life0Contains data Life expectancy, 1998 Observations: 68 26 Aug 2023 20:06 Variables: 6Sorted by:--------------------------------------------------------------------------------------------------------------------------------------------------------------Frame: life1Contains data U.S. life expectancy, 1900-1999 Observations: 100 26 Aug 2023 20:06 Variables: 10Sorted by: year--------------------------------------------------------------------------------------------------------------------------------------------------------------Frame: life2Contains data U.S. life expectancy, 1900-1940 Observations: 41 26 Aug 2023 20:06 Variables: 2Sorted by: year-------------------------------------------------------------------------------
Frameset 命令还存储大量 r 结果,以跟踪正在发生的事情,例如,正在保存或加载的 frame 的子集,每个 frame 中的数据在内存中是否发生了变化,等等。
与 .dta 文件一样,Stata 提供 .dtas 文件的低级描述。help-dtas 提供了读取和写入其他软件 .dtas 文件所需的所有详细信息。
Frameset 命令的语法和选项很自然地遵循数据集命令的句法和选项,如 save (保存), use (使用), 和 describe (描述)。例如,dataset 和 frameset 命令以相同的方式处理标签、空数据集、描述数据集的详细程度等。
Stata 使用其原生的 zipfile 在 frames save 中压缩帧集文件,并在 frames use (帧使用)中 unzipfile (解压缩文件)。用户可以指定帧保存的压缩级别。这可以通过两种方式完成:通过 complevel(#)选项或通过 set dtascomplevel#。# 是介于0和9之间的整数——0表示无压缩,9表示最大压缩。默认值为1。例如,可以通过键入以下命令在具有最大压缩的磁盘上保存和替换 life.dtas。
. frames save life, frames(life0 life1 life2) complevel(9) replacefile life.dtas saved
请注意,frames 和 framesets 是建立在数据集之上的。这意味着,如果 frames 和 framesets 对您没有实际使用意义,您可以以与以前完全相同的方式继续使用数据集。你可能需要知道的唯一一件事是,当你使用一个数据集时,它默认会进入一个 frame——而这个 frame 被命名为 default,这并不奇怪。在一天结束时,即使使用 frame,也可以在任何给定时间交互式地使用一个数据集或一个 frame。
Stata 18新增功能:Alias variables across frames(跨帧的别名变量)
在本节中,作者将描述如何使用别名变量以高效内存的方式跨帧访问变量。
不同 frame 中的两个数据集可以通过具有匹配变量来关联。如前所述,可以通过基于公共变量将当前 frame 中的观测值与相关 frame 中的观察值进行匹配,将 frame 与 frlink 链接起来。
使用 frlink 创建链接后,可以使用 fralias-add 定义变量别名——引用 linked frames 中变量的名称。
下面是添加别名变量的示例。首先,让我们像上面那样在内存中设置 auto 和 cars frames。
. clear all. frame create auto. frame change auto. sysuse auto(1978 automobile data). frame copy auto cars. frame cars: generate newvar = runiform(). pwf (current frame is auto)
这两个 frame 是相同的,除了添加到 cars 的变量 newvar。从当前的框架 auto,您可以根据公共变量 make 创建与 car 的一对一链接:
. frlink 1:1 make, frame(cars)(all observations in frame auto matched)
现在,可以在当前 frame auto中创建一个别名变量,比如 newvar,以访问汽车中的变量 newvar:
. fralias add newvar, from(cars)(1 variable aliased from linked frame)
这里的别名变量与它所指向的变量同名,但可以不同。作者将在下一个示例中演示如何操作。
本质上,fralias-add 定义了从 current frame 到 linked frames 中变量的引用。通过引用,您可以使用链接的变量,而无需在当前框架中复制它们。这些引用占用的内存很少;变量实际上只存储在一个 frame 或数据集中,但可以在不同的 frame 中使用。
以下是关于 frlink 的更多评论,fralias 是基于 frlink 的。使用 frlink 时,会在当前 frame 中创建一个新变量。它引用链接的 frame 。默认情况下,新变量以链接的 frame 命名。但是使用 generate()选项可以生成不同的变量名。
此外,frlink 完成的观测值与常见变量的匹配可以是一对一或多对一。更有用的是,frlink 还将处理不同 frame 中常见但名称不同的变量。此外,frlink 可以在变量名中使用通配符*来匹配变量组。如果数据发生更改或帧被重命名,则可以使用 frlink rebuild 或通过删除链接变量来删除链接。
Fralias-add 创建的别名变量与数据集中的任何其他变量一样处理,但需要注意的是,不允许更改其值。对于给定的别名变量,如果在其所在的 linked frames 中更改相应变量的值,则在下次使用别名变量时,更改后的值将自动可用。因此,在一个 frame 中改变变量就足够了,并且这种变化反映在所有引用它们的框架中。
别名变量允许许多 frame 具有相同的变量,就好像它属于所有 frame 一样,但变量仅存储在一个 frame 中。这样可以避免创建重复的变量或使用诸如 merge 或 frget 之类的昂贵命令。例如,后者从具有大内存占用的 linked frames 帧中复制变量,尤其是对于 double 和 string 等的数据类型。相比之下,别名变量只是内存中的引用,具有较小的固定内存占用。因此,使用别名变量可以节省内存,并有助于将所有 frame 保存在内存中,从而使 Stata 保持快速灵活。
Frameset 和别名变量示例
在本节中,作者将提供一个更完整的示例,并深入研究 framesets 和别名变量命令的其他功能。
假设你正在进行一个关于美国得克萨斯州收入水平的项目,并希望分析个人和县一级的数据(美国每个州都包括县)。
您使用的是两个 Stata 数据集:persons.dta 和 txcounty.dta 。您可以在两个帧中加载这两个数据集,例如,persons 和 country,如下所示:
. clear all. frame create persons. frame change persons. webuse persons. frame create counties. frame change counties. webuse txcounty(Median income in Texas counties)
您可以使用 frame 前缀来描述这两个 frame:
. frame persons: describeContains data from /d/file/gt/2023-09/3l5ongxs.dta Observations: 20 Variables: 3 16 Apr 2022 13:36 (_dta has notes)----------------------------------------------------------------------Variable Storage Display Value name type format label Variable label----------------------------------------------------------------------personid byte %9.0g Person IDcountyid byte %9.0g County IDincome float %9.0g Household income----------------------------------------------------------------------Sorted by:. frame counties: describeContains data from /d/file/gt/2023-09/mffiyp23vop.dta Observations: 8 Median income in Texas counties Variables: 2 30 Dec 2022 06:13 (_dta has notes)-------------------------------------------------------------------------------Variable Storage Display Value name type format label Variable label-------------------------------------------------------------------------------countyid byte %9.0g cty County IDmedian_income float %9.0g Household median income-------------------------------------------------------------------------------Sorted by:
通过 clear all 以上内容,我们自动从一个名为 default 的空工作 frame 开始。然后,我们在 default 的基础上添加了两个 frame 。我们可以列出内存中的 frame ,并用
. frames dir counties 8 x 2; Median income in Texas counties default 0 x 0 persons 20 x 3; persons.dta. pwf (current frame is counties)
Counties 是当前的 frame,因为这是我们更改为的最后一个 frame。如果我们想与 persons 合作,我们必须更改为该 frame:
. frame change persons
由于 frames persons 和 counties 有共同的变量 countyid,我们可以使用 frlink 将当前 frame person 链接到 frame county,基于 countyid。因为许多 person 属于同一个 county,所以这里的匹配是多对一(m:1):
. frlink m:1 countyid, frame(counties)(all observations in frame persons matched)
匹配的变量不必具有相同的名称。在这种情况下进行链接是相当直接的。
请注意,上面的 frlink 命令在 persons 中创建了一个新变量,并命名为 countries 。它以 frame linked 命名。选项 generate( ) 本可以在 frlink 中用于创建不同的变量名。新变量的值与各县的观测值相匹配。
现在,您可以使用 frames save 来在磁盘上保存 frame persons 和所有其他链接到它的 frame ,方法是指定 linked 选项;所有 frame 都保存在文件 myproject.dtas 中:
. frames save myproject, frames(persons) linkedfile myproject.dtas saved
请注意,在这种情况下,只有 frame counties 链接到当前 frame,给定上面的 frlink 命令。因此,除了 persons 之外,counties 也保存在 myproject.dtas 中。
接下来,您可以重置内存中的所有 frame,稍后提醒自己 myproject.dtas 中有什么 frames describe(我们使用选项 进行紧凑描述):
. frames reset. frames describe using myproject, --------------------------------------------Frame: personspersonid countyid income counties----------------------------------------------------------------------------------------Frame: countiescountyid median_income--------------------------------------------
您可以将保存在 myproject.dtas 中的所有 frames 一起加载到内存中使用:
. frames use myproject, frames(_all) counties 8 x 2; Median income in Texas counties persons 20 x 4
请注意,在这一点上,当前 frame 是 default 的,正如 pwf 所揭示的:
. pwf (current frame is default)
即使已经在内存中加载了两个 frames ,当前 frame(本例中为 default frame)也不会随着 frames use 而更改。要使用其中一个加载的 frame,例如 persons ,您必须明确将其指定为 working frame:
. frame change persons
接下来,您想将 persons 收入与该 counties 的收入中位数进行比较。中等收入可用于框架 counties 。我们知道,根据上述 frlink 命令, persons 与各 counties 有关联。我们可以从当前 frame( persons )中验证现有的联系
. frlink dir (1 frlink variable found) ----------------------------------------------------------------------------- counties created by frlink m:1 countyid, frame(counties) ----------------------------------------------------------------------------- Note: Type "frlink describe varname" to find out more, including whether the variable is still valid.
要访问 frame counties 中的变量median_income,可以添加一个别名变量,例如中位数,以引用变量,如下所示:
. fralias add median = median_income, from(counties)(1 variable aliased from linked frame)
可以使用描述别名变量
. fralias describe median----------------------------------------------------Alias Type Target Link Frame----------------------------------------------------median float median_income counties counties----------------------------------------------------
现在,您可以在包含可变中位数的 frame persons 中运行分析。简单地说,在这里,你可以找到个人收入与相应县收入中值的比率:
. generate ratio = income/median
请注意,别名变量的 median 仅引用 counties 中的 median_income,这几乎不消耗内存。因此,您可以像处理 frame 的一部分一样处理变量,只需很少的内存开销。但是你不能改变变量;它只能在 frame countries中更改。变量中的任何更改都将在引用它的所有 frame 中可用。