作者:許夢潔 (中山大學)
Email: StataChina@163.com
Stata連享會 計量專題 || 公眾號合集
點擊查看完整推文列表(西安, 3月26-29日,司繼春-遊萬海 主講; 內附助教招聘)
連享會-文本分析與爬蟲專題班,西北工業大學,2020.3.26-29
語言:Python
方法:拆分文件
目的:提高運行速度
一、任務描述
對 2010 年後 49083 條上市公司股權變更數據(Firm-Event 觀測)分別統計每個事件發生前後 15 天公司:
二、數據描述數據集總樣本數2010年後的樣本數上市公司股權變更記錄5758449083上市公司公告記錄27870262758934上市公司日超額收益97494645534947
三、解決思路
在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)<LIMIT:
continue
file_name = str(file_count)+".txt"
with open(file_name,'w') as file:
for url in url_list[:-1]:
file.write(url)
file.write(url_list[-1].strip())
url_list = []
file_count += 1
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天公告數22014-03-210.1928260.0639818622014-08-29-0.057021-0.03309719622014-09-16-0.031721-0.03163561622015-01-24-0.010155-0.10772231322015-01-28-0.069575-0.04520161022015-07-110.356788-0.126676151622015-07-25-0.1925250.0095171222015-08-040.019329-0.12050872922015-08-270.142061-0.048584221122015-12-070.2429670.221147111622015-12-090.3533910.276527121522015-12-160.2687260.124451172622016-07-07-0.27522-0.133624461822016-08-050.2940570.1905699822016-08-090.2950280.076121715
關於我們歡迎加入Stata連享會(公眾號: StataChina)