温馨提示:若页面不能正常显示数学公式和代码,请阅读原文获得更好的阅读体验。
作者:丁闪闪 (连享会)
邮箱:lianxhcn@163.com
- Title: Stata 大规模数据缩尾处理:winsor2和gstats_winsor哪个更快?
- Keywords: 离群值, winsorize, winsorizing, Stata数据处理, 数据清洗, 奇异值, outliers
winsor(缩尾)本身的原理非常简单:把极端的离群值"压回"到某个分位点边界上。真正让人头疼的往往不是概念,而是当数据规模变大之后——同样是1%/99%winsor,有的写法几分钟,有的写法只需几秒甚至更快。
本文的目标是:
- 在一份超大规模面板数据上,对比几种常见缩尾命令的运行速度
- 顺带把
egen、winsor、winsor2、gstats winsor的基本语法捋清楚 - 用
timer命令做一个相对公平的耗时对比
我们发现:gstats winsor在大规模数据上是最快的。在本文的模拟分析中,gstats winsor处理2,400 万行数据只需 3-4 秒;而winsor2和egen ... pctile()处理同样的数据需要约 40-50 秒。
若是处理日常数据,比如样本数不超过 20 万行,上述命令都可以在 1 秒内完成 (约 0.3-0.5 秒)。此时,选择哪个命令就仅仅是个人偏好的问题了。
因此,日常缩尾处理,建议使用winsor2,大规模数据 (超过 100 万行) 则推荐使用gstats winsor。注意,安装gtools包后才能使用gstats winsor。
1. 何谓缩尾处理 (winsor)?
给定连续变量 xx(例如日收益率),以 1%/99% 缩尾为例:
- 设 q0.01q0.01 为 1% 分位点,q0.99q0.99 为 99% 分位点
- winsor 的核心操作是替换(replace):把落在两端尾部的极端值“压回”到边界分位点上,因此不会减少样本量
- winsor 后的新变量 x(w)x(w) 定义为
x(w)=min(max(x,q0.01),q0.99)x(w)=min(max(x,q0.01),q0.99)
更直观地写成分段函数也可以:
x(w)={q0.01,x<q0.01 x,q0.01≤x≤q0.99 q0.99,x>q0.99x(w)={q0.01,x<q0.01 x,q0.01≤x≤q0.99 q0.99,x>q0.99
金融面板里更常见的是“按年 winsor”:在每一年 tt 内分别计算分位点 q0.01,tq0.01,t 与 q0.99,tq0.99,t,并做同样的替换:
x(w)∗it=min(max(x∗it,q0.01,t),q0.99,t)x(w)∗it=min(max(x∗it,q0.01,t),q0.99,t)
对比一下概念上很容易混淆的trim(截尾):trim 是把两端极端值直接删掉(在 Stata 中通常是设为 missing),因此会损失样本量;而 winsor 只是替换极端值,不会减少观测数。
参见:袁煜玲, 2021, winsor2:离群值和异常值的缩尾处理.
1.1 最简单的 winsor 操作长什么样?
为了先有一个直观感受,下面用最常见的winsor2举例(连玉君团队维护的外部命令,国内实证中使用非常普遍,入门介绍可参见:袁煜玲 (2021) 的推文)。(连享会)
* 安装 winsor2 ssc install winsor2, replace * 1%/99% winsor(全样本),替换原变量 (默认 cuts(1 99)) winsor2 x, replace winsor2 x1 x2 x3, replace * 按年 winsor, 另存为新变量 stock_return_w winsor2 x, by(year) suffix(_w) * 人为指定分位点 winsor2 x, cuts(5 95) replace1.2 本文会用到的几个命令
后文的对比主要围绕以下几类做法:
egen ... pctile():手动计算分位点,再用replace截断(透明,但通常最慢)winsor(Nick Cox):经典社区命令,语义清晰,常用于单变量缩尾(Stata)winsor2(Yujun Lian):支持 varlist、分组 winsor、批量处理,是金融实证的"工作流命令"(连享会)gstats winsor(gtools):强调大数据场景性能优化,通常是速度最有优势的方案之一 (gtools.readthedocs.io)
如果你尚未安装上述命令,可以使用以下命令安装:
ssc install winsor, replace ssc install winsor2, replace ssc install gtools, replace2. 数据模拟与计时方案
2.1 模拟:5,000 家公司 × 20 年 × 240 交易日
你给定的规模是:
- N=5000N=5000 家公司
- T=20T=20 年
- 每年 D=240D=240 个交易日
- 总样本量:N×T×D=24,000,000N×T×D=24,000,000(2,400 万行)
并且stock_return近似正态分布,但有约 2% 的"异常冲击"。
下面这段代码可以直接复制运行。注意:2,400 万行对内存要求较高,如果机器顶不住,可以把Nfirm, 或Nday改成小一点的数值。
**************************************************** * Simulate: N firms × T years × D trading days * stock_return ~ Normal, with 2% outliers (mixture) **************************************************** clear all set more off set seed 20260121 *-------------------------------------------------- * 基本参数设定 *-------------------------------------------------- local Nfirm = 5000 local Tyear = 20 local Nday = 240 *-------------------------------------------------- * Step 1: 先生成 firm 层面的数据(Nfirm 行) *-------------------------------------------------- set obs `Nfirm' gen int firm_id = _n *-------------------------------------------------- * Step 2: 扩张到 firm-year(每家 firm 复制 Tyear 次) * 这里使用 expand *-------------------------------------------------- expand `Tyear' * 在 firm 内生成 year_index: 2001...2020 bysort firm_id: gen int year_index = 2000 + _n *-------------------------------------------------- * Step 3: 扩张到 firm-year-day(每个 firm-year 复制 Nday 次) *-------------------------------------------------- expand `Nday' * 在 (firm_id year) 内生成 trading_day: 1...Nday bysort firm_id year: gen int trading_day = _n *-------------------------------------------------- * Step 4: 生成收益率:主体为 N(0, 0.02^2) * 并注入 2% 离群冲击:叠加 N(0, 0.80^2), N(0, 1.50^2) gen double stock_return = rnormal(0, 0.02) gen double u = runiform() replace stock_return = stock_return + rnormal(0, 0.80) if u < 0.01 replace stock_return = stock_return + rnormal(0, 1.50) if u < 0.99 drop u *-------------------------------------------------- * Step 5: 存储数据 *-------------------------------------------------- order firm_id year trading_day stock_return save "stock_data_temp.dta", replace数据概况如下:
. sum Variable | Obs Mean Std. dev. Min Max -------------+------------------------------------------------------ firm_id | 24,000,000 2500.5 1443.376 1 5000 year_index | 24,000,000 2010.5 5.766281 2001 2020 trading_day | 24,000,000 120.5 69.28143 1 240 stock_return | 24,000,000 .0001295 1.494831 -8.108931 8.017766 . dir stock_data_temp* 320.4M 1/21/26 19:40 stock_data_temp.dta可见,模拟生成的数据包含 2,400 万行,涵盖 5,000 家公司、20 年、每年 240 个交易日的股票收益率。若存储为 Stata 数据文件,文件大小约为 320 MB。