为了复刻《Predicting Returns with Text Data》这篇文章进行的研究。2020年版本在如下链接中,尝试的方法会以2019年版本为主。
Predicting Returns with Text Dataposeidon01.ssrn.com项目:在不依靠已存在字典的情况下理解文本的sentimental structure
为了减轻workload和计算量,只会使用10-20个股票、1-2年内的数据。
11/21:主要任务移到Methodology部分,预处理部分可能按需要稍微调整
内容总结
- 获取数据
- 新闻来源:NEXIS UNI
- 股票数据:CRSP
- 数据读取和预处理
- 去专有名词
- 标准化
- stemming & lemmazation
- 标记化
- 去停用词
- 词袋与词向量
- crsp获取前两个、前一个、下一个交易日的股票价格
数据获取
1)无法获取Dow Jones Newswire数据,其他来源数据质量不理想
按照原文所述,文本内容来自Dow Jones Newswires,实际上学校没有订阅数据,价格也比较贵(2.5万还是5万美金/年),没法自费购买。备选的方法有Nexis Uni资料和自己写爬虫,各有优缺点:
Nexis Uni | Web Scrapping (New York Times) |
---|---|
+1)新闻数量多,涵盖内容丰富 | +1)消息来源可靠,真实 |
+2)一次性可以下载100条文章 | +2)每天都有1条新闻 |
-1)全程需要手动下载数据,缩小文章搜索范围 | -1)如果不用seleunim进行点击操作,单个股票/公司只能爬9条新闻(技术原因) |
-2)文章内容有重复 | -2)消息来源和内容单一,可能只包括金融方面信息 |
√ | × |
最后决定使用Nexis Uni数据,它的优点也比较明显,缺点相对来说可以接受。同时,(两个)数据仍然有无法解决问题:
- 发布时间只精确到天,所以无法按照原文精确到小时的日起分类方法;
2)CRSP股票数据
订阅的权威数据,clean data,不需要额外操作,但是缺少2020年的数据。
//********************************************************************//
数据读取和预处理
1)从解压好的新闻里读取文件,使用reg ex提取文章内容和时间
# read docx file
2)文本数据预处理
# 需要的包
3)CRSP数据处理
对表格进行self join,获得
4)合并文本数据和CRSP数据
主要包括时间、单词、词向量、股票价格
下面是详细介绍和日常进展汇报。
1 数据准备
文章里的文本来自Dow Jones Newswires,股票回报数据来自CRSP,由于学校没有买Dow Jones Newswires,所以使用其他数据来源。
(1)选取Nexis Uni作为文本来源
这家新闻覆盖面比较全,日期、来源、地域、语言、领域都可以选择,一次性下载100篇文章基本也符合需要了,毕竟文本量太大的话处理起来比较费时间。目前暂定以New York Times作为消息来源,主要是为了避免多家媒体对于同一事情以不同方式做了处理、单一消息源可以cover尽量多的时间范围(100条新闻上限),不过问题还是有几个:
- 单独下载的文件会以.docx存在,暂时不知道怎么读取(11/11 已解决)
- 必须手动下载文件,读取多个文件包里的多个内容暂时不知道怎么做(11/12 已解决)
- 不知道怎么精确提取文本内容和时间,去掉不必要的东西(11/15 基本解决)
(2)选取web scraping New York Times作为文本来源
理由基本同上,但需要先搜公司名,在搜索结果中再把新闻一条一条提取出来,缺点是只能提取9条新闻,如果需要点击“show more”需要再用selenium包,没有那么多时间研究。
关于获取新闻链接的函数已经写好了(2020/11/11),以苹果公司为例,获取结果如下:
[
同样,这个函数/搜索方式的缺点是不可以用ticker搜索
(3)CRSP数据
学校买了,下就完事了
数据质量
2020/11/13:
关于这部分,最主要的问题是对文本内容和质量的担忧。
- 按照论文所述,每篇文章可能对应一个或者多个公司,也可能完全没有对应,目前不知道研究者是怎么移除不符合要求的文章的
- 由于Nexis Uni上的文章的发布时间没有精确到小时,无法按照文中给出的方法合并文章,按原文所说,一篇在(t-1) 4 p.m.后且(t)4p.m.前的文章要和公司三天(t-2到t+1)的收盘价挂钩
那暂且假设,前一天(t-1)的文章可能会对后一天(t)的开盘价造成一定影响,当天的股票(姑且不管发布时间)对于收盘价有影响
- 所谓“Chained Article”,文章并没有解释
- CRSP数据只更新到了2019/12/31,之后并未给出
- 研究人员自己也指出,新闻网的数据来源于多个媒体,可能会对同一事件有冗余覆盖,尽管在训练和测试集中没有影响,但对于样本外的分析会有反作用(adverse impact on our out-of-sample analysis)。
2 预处理 Pre-Processing
根据2020年版本,
关于 文本部分的处理,首先 去掉专有名词(proper noun),接下来 正则化文本内容。
第一步:
1)所有单词转成小写;
2)扩展缩写,列如“haven't”转化为“have not”;
3)去掉数字,标点,特殊符号,非英语单词;
第二步:通过stemming和lemmazation,将不同时态、不同单复数形式等单词转化为同词根单词。
第三步:tokenize文本
第四步:去掉停用词。
第五步:获取词向量,以"bag-of-words"表示。
实际操作起来的时候,我不太能明白为什么研究者在第三步才分开单词。基本按照原文提供的步骤,主要的思路如下:
- 在读取文本的时候就扩展缩写,具体方法来自stackoverflow,
- 使用nltk.tag.pos_tag()获取单词的成分,移除专有名词(NNP和NNPS)[1]
- tokenize
- 使用isalpha()和lower()去标点
- stemming,lemmazation
- 去掉停用词
- 获取词向量
去掉停用词以后,300篇文章大约提供了5300个单词,其中有一些在stemming和lemmazation之后不是“完整”的单词。进一步统计了特定出现频率单词的个数,例如只出现1-2次的单词个数,并移除:
移除1-2词频单词后,还有3000+单词。
11/19重要进展(目前依然只有296篇文章):
之前一直很疑惑,为什么研究人员在预处理部分采取了先标准化,stemming和lemmazation,再标记化的决定。个人是觉得,既然stemming和lemmazation要求input是list,那直接用nltk.word_tokenize()——一下子就标记化并且改为word list,然后stemming更方便。但这样会带来一个问题:
由于一些动词可以采用不同的格式,列如study有studies, studying, studied等形式,在标记化时解释器会当做不同的单词对待,即便这些格式都指向同一个单词的同一个意思。
正因为如此,才需要先stemming和lemmazation。
但是,在一些教程、实际操作中我也发现,stemming出来的单词并不总是“real words”,比如company会变成compani,虽然说也能推断出原来的单词,却有一点影响理解。所以我只用了lemmazation,把pos设定为v,假定动词对于文章更重要。
大部分预处理都采取研究人员的操作顺序。
现在的顺序 | 原来顺序(错误) |
---|---|
(标准化:在读取文章时就把缩写转扩写) | (标准化:在读取文章时就把缩写转扩写) |
去掉专有名词 | 去掉专有名词 |
1)标准化:去标点、特殊字符、非英语单词,去数字,转小写,(扩写) | 3) 标记化 |
2)lemmazation | 1)标准化:去标点、特殊字符、非英语单词,去数字,转小写,(扩写) |
3) 标记化 | 2)stemming和lemmazation |
4) 去停用词,附加去低频词 | 4) 去停用词,附加去低频词 |
5)词向量与词袋 | 5)词向量与词袋 |
好处有三个(算是3个):
1)留了更少的单词,对于之后的研究分析很有帮助,提升效率,应该也能省内存吧,比如说原先不去低频词大约有5300+单词,现在只有4690个,去掉低频单词后也从3360+单词变为约2900单词
2)词云图里的单词都是real words,不会出现compani,ha, wa,这种奇怪的词,也不会同时出现said和say
3)个人觉得代码更简洁,运行效率更高了,比如原来stemming和lemmazation一共要处理4s,现在大约400ms(毕竟不需要Stemming);原来移除停用词是299ms,现在200ms出头(虽然在毫秒级别上这点进步没什么意义)
进展汇报:
- 2020/11/11:
- 判断新闻数据源的优劣,尝试web scraping纽约时报网站
- 写了.docx读取方法
- 2020/11/12:
- Nexis Uni无法保证New York Times信息的即时性;根据现在的筛选,一天之内依然有多个新闻(其实是好事,一条新闻判断涨跌很武断);筛选个数20条,具体筛选条件如下:
-
- 写了读取目录下文件的函数
from
-
- Nexis Uni下载的数据发现一些问题:
- 存在标题完全一致的文章,如果下载20篇,2篇重复,就只有19篇独立文章
- 由于每天都有多条新闻,20篇文章可能只cover了3天的新闻量
- 写了从文章里读取时间、文章内容,由于文章内容是从Body开始,但是结束的地方没有固定的格式,有的地方是有Graphic标记,有的直接就是by author,所以不好判断结束。最后使用了笨办法获取文本,获取文本list中文本开始和结束的(也就是body标记和by标记的地方)index,取出文本,合并为string,最后生成一个带有ticker,发布时间,标题,内容的DataFrame
- Nexis Uni下载的数据发现一些问题:
# read docx file in the directory
-
- 即使获取了如上信息,时间格式仍然不统一,在之后data clean部分做处理:
-
- 使用regex取出时间,按月,日,年分别放入三个列,去掉空行(因为日期为空),并改变日期格式
# regex, extract time and drop na
- 2020/11/13
- 重新获取新闻数据,时间范围改为2019/01/31 - 2019/12/31,按newest-oldest取100篇文章(实际上并不能cover很长的日期),group duplicates
-
- 转换时间格式后,获取weekday信息,简单查看新闻数量分布
- 使用上面笨办法取出的文本可能依然含有“Body”这个东西,同一件事用不同title还是会出现
- 11/14
- 正在研究另一篇NLP入门[2],并无实质性进展
- 从NLP入门里看到的字母判断(去掉标点)、大小写转化:
#Empty list to store words:
-
- 获取词频的method,参考了上述tutorial给出的代码,并且画出分布,具体例子明天写(这篇文章真的非常有用!)
# Import required libraries:
-
- 停用词等部分不抄了,尊重原作者。整理一下思路:先拆分出单词(tokenize),接着看一下最初的词频;去掉标点(no punctuation),再看词频和词的数量;去停用词,再查看词频+数量
- 接下来预计做词云图
- 11/15:
- 按照tutorial,用一篇文章作为sample(简称sample),一直做到了词云图
- 优化NLP代码,使其简洁、更精确提取文章内容(使用了正则表达)
- 开始进行预处理工作,去掉专有名词(proper noun),移除数字、标点、特殊符号,无法完成缩写转扩写(haven't→have not),pyconstraction包无法安装,会尝试写函数修改
# 去专有名词
- 11/16:
- 继续完成预处理部分,已经做好了stemming,lemmazation,去停用词,去低频词部分
# remove low frequency words
df["words"] = df.apply(lambda row: removeLowFreq(row['clean_words'], freq1), axis = 1)
df["words"] = df.apply(lambda row: removeLowFreq(row['words'], freq2), axis = 1)
df["words_len"] = df["words"].str.len()
-
- 准备整合函数,开始对CRSP数据进行处理
- 11/17:
- 完成bag-of-words,并以DataFrame格式呈现,尝试在countvector里再筛选掉低词频单词,目前单词数量3342
-
- 目前已经处理好了这几部分:文章题目,文章发布时间,词向量;需要处理的部分:CRSP数据
- CRSP数据遇到了问题,苹果(Apple, Inc)的Ticker APPL提交以后没法取到数据
- 合并表格的时候用到BOW(bag-of-words),文本title和日期,这三个能合并起来;但是加上CRSP还不成功,因为一个同一个日期有好几个股票的数据,对应的时候必须选择TICKER和日期同时进行合并;另外,CRSP的数据里的date似乎不是unique....
- 目前已经处理好了这几部分:文章题目,文章发布时间,词向量;需要处理的部分:CRSP数据
- 11/18:
- 昨天说到crsp数据的问题,处理方式如下:1)按ticker去掉重复日期(可能数据更新不及时导致这个问题)2)时间格式从MM/DD/YYYY改为MM-DD-YYYY,保持和文本部分时间格式一致,这样解决了即使成功连接两表也会让右侧值为NaN的问题
- 关于合并时的新问题,报错。准确说我也不知道为什么,但是把文本数据存在电脑上再读取一次,就能顺利合并了。
The column label 'date' is not unique
-
- 准备着手Methodology部分,对于CRSP数据,以文章发布时间为t计算,还需要(t-2), (t-1), (t+1)用于训练或预测的股价/回报,为了整合这个数据我想了好几个小时,
- 先是通过pd.merge()(即上面合并表格方法),通过改变date的办法(为了得到对应t-1,把所有的t'都改为t+1,然后连接两表),还学会了改后缀(suffix),结果只有MSFT的结果没有BABA
- pd.shift(),结果全部结果都shift,压根就无视了ticker
- pd.shift() + groupby,顺利完成任务,不过缺点是所有t都代表了交易日,不是自然日,也就是说t是12-24的话,t+1是12-26而不是12-25(圣诞节是法定节日)
- 准备着手Methodology部分,对于CRSP数据,以文章发布时间为t计算,还需要(t-2), (t-1), (t+1)用于训练或预测的股价/回报,为了整合这个数据我想了好几个小时,
- 11/19:
- 调整预处理顺序,改进代码,报告已经在文本里更新
- 优先lemmazation再tokenize
- 去掉stemming
- 优化代码
- 更新词云图
- 整合文档
- 调整预处理顺序,改进代码,报告已经在文本里更新
- 11/20:
- 整合crsp数据,对于时间t',获取(t'-2),(t'-1),(t'+1)时的收盘价格,其中t为交易日,不是自然日
- 在已经做好词向量+文章发布时间t的数据表内,合并crsp,对于任意时间t,有t-2,t-1,t+1的收盘价
- 11/21
- 解决了apple, inc无对应股票价格的问题:ticker写错了(是我瞎)
- 加入1个新的股票:Tesla,获取一百条新闻(其中有大量重复)
- 删除去低频词,原因:
- 1)词频计算方式变化。在后文作者描述词频f计算方式时,需要的是包含词j的文章的数量,也就是说如果词j在一篇文章里出现2词,那么按原方法计算词频是2,现在方法计算是1,举例来说,词“apple”在3篇文章里出现了1次,2次,10次,按原方法计算词频是13词,很可能不被去掉;按照现在的方法是3次,需要去掉
- 2)去掉低频词可以通过调整超参k去掉,简洁代码
- 调整CRSP与词向量合并参数,从inner join 改为left,在t和(t'-2),(t'-1),(t'+1)均为NaN时保留文章
- 想把新闻搜索结果按“newest to oldest”改为按相关度排序
- 进入Methodology部分
参考
- ^https://stackoverflow.com/questions/39634222/is-there-a-way-to-remove-proper-nouns-from-a-sentence-using-python
- ^https://link.zhihu.com/?target=https%3A//medium.com/towards-artificial-intelligence/natural-language-processing-nlp-with-python-tutorial-for-beginners-1f54e610a1a0%237d22