作者:许梦洁 (中山大学)
Stata 连享会: 知乎 | | 码云
连享会计量方法专题……
Stata连享会 计量专题 || 精品课程 || 推文集锦
2020寒假Stata现场班(北京, 1月8-17日,连玉君-江艇主讲)
2020连享会-文本分析与爬虫-现场班
西安, 3月26-29日,司继春-游万海 主讲 (附助教招聘)
语言:Python
方法:拆分文件
目的:提高运行速度
一、任务描述
对2010年后49083条上市公司股权变更数据(Firm-Event 观测)分别统计每个事件发生前后15天公司:
- 发布的临时公告数
- 累计超额收益(CAR)
二、数据描述
数据集 | 总样本数 | 2010年后的样本数 |
---|---|---|
上市公司股权变更记录 | 57584 | 49083 |
上市公司公告记录 | 2787026 | 2758934 |
上市公司日超额收益 | 9749464 | 5534947 |
三、解决思路
在Python
构造一个类似于 Excel
中的 countif 函数即可。具体见我上一篇博文 [百万级大样本中的 countif 实现]。
四、潜在问题
虽然按照上一篇文章的思路也能基本完成任务,但是程序运行非常慢,光跑一次统计窗口期公告数据的程序就要27个小时,所以必须优化程序以提高运行速度。
五、优化思路
由于全样本非常大,上篇博文的程序相当于是对每一个公司股东股权变动事件在全部的公告池(2758934条记录)中进行搜索,显然这样做是无效率的。
因此,此次程序优化的主要思路是分别拆分 49083 条公司股东股权变动事件和 2758934 条上市公司公告记录,并将两类拆分的文件对应起来。举个例子:
- 第一步: 将股票代码在 000001 到 000049 的公司股权变动事件拆出来
- 第二步: 将股票代码在000001 到 000049 的公司公告拆出来形成一个小的公告搜索池,然后对这部分公司的股权事件在这个小搜索池里统计公告记录。
通过拆分文件得到的精确匹配大大减小了每一个事件的搜索范围,可以大幅提高程序运行效率。优化后跑一次同样的统计窗口期公告数据程序仅需 12 分钟,是原来运行速度的 135 倍。
六、核心代码(以统计窗口期CAR为例)
1. 初步拆分
为了保证运行效率最高,首先平均分拆 CAR 序列,遍历股票日超额收益序列数据,设定阈值为 50000,每 50000 条数据拆出一个文件,以 "CAR+编号" 为文件名 (eg: CAR109.txt
)。最后共拆出了 110 个文件,Python 代码如下:
LIMIT = 50000
file_count = 0
url_list = []
with open("股票日超额收益序列.txt") as f:
for line in f:
url_list.append(line)
if len(url_list)
2. 根据分拆文件记录拆分股票节点
遍历拆出来的 110 个 CAR 文件,分别记录每个文件最后一个观测的股票代码,并逐条写入 “拆分节点.txt” 中。Python 代码如下:
import pandas as pd
for i in range(111):
file = "CAR"+repr(i)+".txt"
data = pd.read_table(file, header=None, encoding='utf-8', delim_whitespace=True)
data.columns = ['stkcd', '日期序列', '日超额收益']
stkcdlist = data.loc[:,'stkcd']
end = str(stkcdlist[len(stkcdlist)-1])
with open("拆分节点.txt",'a') as f:
f.write(end+"\n")
print(end)
f.close()
连享会计量方法专题……
3. 根据拆分节点拆分事件列表并再拆分 CAR 列表
由于初步拆分是根据样本数拆分,因此出现了同一只股票不同日期的 CAR 会被拆到两个不同文件的情况,十分不利于后面股权变更文件与 CAR 文件拆分后实现完美匹配。
因此,需要根据第二步得到拆分节点处的股票代码对 CAR 列表再拆分,并同时拆分股权变更列表。经过这一步后拆分后的两种文件就可以实现精确匹配。
此外,由于事先已经对股权变更文件以及 CAR 文件根据股票代码以及日期进行排过序,因此接下来的拆分只需逐行遍历,判断遍历到的观测股票代码与拆分节点处的股票代码的关系,如果遍历处股票代码大于当前拆分节点,则保存一个分拆文件,清空相关变量并开启下一个分拆文件。Python 代码如下:
import pandas as pd
dataf = pd.read_table("拆分节点.txt",header=None,encoding='utf-8',delim_whitespace=True)
dataf.columns = ['节点']
LIMIT = dataf.loc[:,'节点']
file_count = 0
url_list = []
line_count = 0
#这里的分拆文件也可以是公司股权变更事件
with open("股票日超额收益序列.txt") as f:
for line in f:
url_list.append(line)
line_count += 1
currentstkcd = int(line.split("\t")[0])
print(currentstkcd)
try:
print(LIMIT[file_count])
if (currentstkcd < LIMIT[file_count]):
continue
except:
print("已经是最后一个观测")
file_name = "CAR"+str(file_count)+".txt"
print(file_name)
with open(file_name,'w') as file:
try:
file.write(left)
except:
pass
for url in url_list[:-1]:
file.write(url)
left = url_list[-1]
url_list = []
file_count += 1
4. 基于拆分后的事件列表和日期序列统计数据
一一对应地拆完大文件之后就可以在缩小的搜索范围里countif啦,这部分思路见上一篇博文百万级大样本中的countif实现。跑完49083条数据的结果只需要12分钟,简直是飞一般的感觉(`・ω・´)。
import pandas as pd
from datetime import *
timespan = timedelta(days=1)
def getlist(add):
data = pd.read_table(add,encoding='utf-8',delim_whitespace=True)
data.columns=['stkcd','日期序列','日超额收益']
return(data)
def 区间计数(股票代码,减持日期,前置窗口长度,后置窗口长度):
减持时间戳 = datetime.strptime(减持日期,"%Y-%m-%d")
开始日期 = (减持时间戳-timespan*前置窗口长度).strftime("%Y-%m-%d")
print(开始日期)
结束日期 = (减持时间戳+timespan*后置窗口长度).strftime("%Y-%m-%d")
print(结束日期)
clist = data.loc[(data['stkcd'] == 股票代码) & (data['日期序列'] <= 结束日期) & (data['日期序列'] >= 开始日期), '日超额收益']
区间CAR = sum(clist)
return(区间CAR)
for i in range(111):
with open("15天CAR统计结果.txt",'a') as g:
try:
f = open("事件" + repr(i)+".txt",'r')
data = getlist("CAR" +repr(i)+".txt")
lines = f.readlines()
for line in lines:
stkcd = int(line.split(',')[0].split("\n")[0])
print(stkcd)
eventdate = line.split(',')[1].split("\n")[0]
事件前15天CAR = 区间计数(stkcd, eventdate, 15, 0)
事件后15天CAR = 区间计数(stkcd, eventdate, 0, 15)
print([stkcd, eventdate, 事件前15天CAR, 事件后15天CAR])
g.write(','.join([repr(stkcd), eventdate, repr(事件前15天CAR), repr(事件后15天CAR)])+'\n')
f.close()
except:
print("skip "+repr(i))
连享会计量方法专题……
七、统计结果样例
股票代码 | 事件日期 | 事件前15天CAR | 事件后15天CAR | 事件前15天公告数 | 事件后15天公告数 |
---|---|---|---|---|---|
2 | 2014-03-21 | 0.192826 | 0.06398 | 18 | 6 |
2 | 2014-08-29 | -0.057021 | -0.033097 | 19 | 6 |
2 | 2014-09-16 | -0.031721 | -0.031635 | 6 | 16 |
2 | 2015-01-24 | -0.010155 | -0.107722 | 3 | 13 |
2 | 2015-01-28 | -0.069575 | -0.045201 | 6 | 10 |
2 | 2015-07-11 | 0.356788 | -0.126676 | 15 | 16 |
2 | 2015-07-25 | -0.192525 | 0.0095 | 17 | 12 |
2 | 2015-08-04 | 0.019329 | -0.120508 | 7 | 29 |
2 | 2015-08-27 | 0.142061 | -0.048584 | 22 | 11 |
2 | 2015-12-07 | 0.242967 | 0.221147 | 11 | 16 |
2 | 2015-12-09 | 0.353391 | 0.276527 | 12 | 15 |
2 | 2015-12-16 | 0.268726 | 0.124451 | 17 | 26 |
2 | 2016-07-07 | -0.27522 | -0.133624 | 46 | 18 |
2 | 2016-08-05 | 0.294057 | 0.190569 | 9 | 8 |
2 | 2016-08-09 | 0.295028 | 0.076121 | 7 | 15 |
关于我们
- 【Stata 连享会(公众号:StataChina)】由中山大学连玉君老师团队创办,旨在定期与大家分享 Stata 应用的各种经验和技巧。
- 公众号推文同步发布于 CSDN-Stata连享会 、-Stata连享会 和 知乎-连玉君Stata专栏。可以在上述网站中搜索关键词
Stata
或Stata连享会
后关注我们。 - 点击推文底部【阅读原文】可以查看推文中的链接并下载相关资料。
- Stata连享会 精彩推文1 || 精彩推文2
联系我们
- 欢迎赐稿: 欢迎将您的文章或笔记投稿至
Stata连享会(公众号: StataChina)
,我们会保留您的署名;录用稿件达五篇
以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。 - 意见和资料: 欢迎您的宝贵意见,您也可以来信索取推文中提及的程序和数据。
- 招募英才: 欢迎加入我们的团队,一起学习 Stata。合作编辑或撰写稿件五篇以上,即可免费获得 Stata 现场培训 (初级或高级选其一) 资格。
- 联系邮件: [email protected]
往期精彩推文
- Stata连享会推文列表
- Stata连享会 精品专题 || 精彩推文
连享会计量方法专题……