一、经济政策不确定性指标

经济政策不确定性(Economic Policy Uncertainty, EPU) 通常是用来衡量经济中政策不确定性水平的一种度量方式。 本文参考

Huang, Yun, and Paul Luk. "Measuring economic policy uncertainty in China." China Economic Review 59 (2020): 101367

1.1 新闻数据库

新闻数据集 | 含 人民日报/经济日报/光明日报 等 7 家媒体(2023.12.18)

人民日报rmrb:       1946-05-15 ~ 2023-12-18
光明日报gmrb:       1985-01-01 ~ 2023-12-18
人民政协报rmzxb:     2008-01-02 ~ 2023-12-18
经济日报jjrb:       2008-01-27 ~ 2023-12-18
中国青年报zqb:     2005-01-01 ~ 2023-12-18
南方周末nfzm:       2008-01-02 ~ 2023-5-31

1.2 算法

Step-1. 选择了114家中国大陆的报纸,其中包括北京、上海、广州和天津等主要城市的报纸。
Step-2. 对于每家报纸,搜索包含以下三个关键词之一的文章:经济、不确定性和政策。这些关键词的中文和英文对照可以在论文的表格1中找到。
Step-3. 将每个月的文章数量按照满足第一个关键词的文章数量进行缩放。
Step-4. 将时间序列标准化,使其在2000年1月至2011年12月期间的标准差为1。 保证所有媒体计算得到的epu是可比的。
Step-5. 对十家报纸的月度序列进行简单平均,并将指标归一化,使其在2000年1月至2011年12月期间的平均值为100。

如果是利用一个媒体进行 类 EPU 指标的构建, 只需用到算法中的前 3 个步骤。



二、基本知识

2.1 查看数据

大邓的 新闻数据集 | 含 人民日报/经济日报/光明日报 等 7 家媒体(2023.12.18)中的所有媒体, 均有csv格式, 内含 date 和 text 两个字段, csv中的每行是一条新闻。

import pandas as pd

df = pd.read_csv('rmrb.csv.gzip', compression='gzip')
df.head()


2.2 日期转化

df['date'] = pd.to_datetime(df['date'])
print('人民日报: ', df['date'].min().date(), '~', df['date'].max().date())

Run

人民日报:  1946-05-15 ~ 2023-12-18

2.3 按日期进行分组

使用日期进行分组, 常见的周期是年Y、月M、日D。 以 df.groupby(pd.Grouper(key='date', freq='M')) 为例, 会得到不同 year-month 及对应的dataframe 。观察 freq 设置成 Y、M、D, 代码运行结果,理解代码字段名含义。

for date, Yfreq_df in df.groupby(pd.Grouper(key='date', freq='Y')):
    print(date, type(Yfreq_df))

Run

1946-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1947-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1948-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1949-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1950-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1951-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1952-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
......
2016-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2017-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2018-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2019-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2020-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2021-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2022-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>

for date, Mfreq_df in df.groupby(pd.Grouper(key='date', freq='M')):
    print(date, type(Mfreq_df))

Run

1946-05-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-06-30 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-07-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-08-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-09-30 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-10-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-11-30 00:00:00 <class 'pandas.core.frame.DataFrame'>
......
2023-05-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-06-30 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-07-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-08-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-09-30 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-10-31 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-11-30 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-31 00:00:00 <class 'pandas.core.frame.DataFrame'>

for date, Dfreq_df in df.groupby(pd.Grouper(key='date', freq='D')):
    print(date, type(Dfreq_df))

Run

1946-05-15 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-05-16 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-05-17 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-05-18 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-05-19 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-05-20 00:00:00 <class 'pandas.core.frame.DataFrame'>
1946-05-21 00:00:00 <class 'pandas.core.frame.DataFrame'>
......
2023-12-11 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-12 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-13 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-14 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-15 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-16 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-17 00:00:00 <class 'pandas.core.frame.DataFrame'>
2023-12-18 00:00:00 <class 'pandas.core.frame.DataFrame'>


2.4 文本操作

dataframe中字段如果是字符串格式, 可以用 .str属性, 该str属性具有以下特色功能。

#某词
word_pattern1 = '不确定'
#某类词
word_pattern2 = '不确定|不明确|波动|震荡'

#是否含某词   
df['text'].str.contains(word_pattern1)  
#是否含某类词   
df['text'].str.contains(word_pattern2)  

#某词出现的次数 
df['text'].str.count(word_pattern1)  


#某类词出现的次数 
df['text'].str.count(word_pattern2)  

在 EPU 的计算中,使用的是contains。另外 即可以是

uncertainty_pattern = '不确定|不明确|波动|震荡|动荡|不稳|未明|不明朗|不清晰|未清晰|难料|难以预料|难以预测|难以预计|难以估计|无法预料|无法预测|无法预计|无法估计|不可预料|不可预测|不可预计|不可估计'


#每条新闻是否出现 uncertainty_pattern , 出现True,不出现False
df['text'].str.contains(uncertainty_pattern)

Run

0          False
1          False
2          False
3          False
4          False
           ...  
2014656    False
2014657    False
2014658    False
2014659    False
2014660    False
Name: text, Length: 2014661, dtype: bool


通过加总True的个数,得到出现 uncertainty_pattern 的新闻记录数

df['text'].str.contains(uncertainty_pattern).sum()

Run

56358

2.4 布尔值的计算

逻辑且 操作,

economic_pattern = '经济|金融'
uncertainty_pattern = '不确定|不明确|波动|震荡|动荡|不稳|未明|不明朗|不清晰|未清晰|难料|难以预料|难以预测|难以预计|难以估计|无法预料|无法预测|无法预计|无法估计|不可预料|不可预测|不可预计|不可估计'
policy_pattern = '政策|制度|体制|战略|措施|规章|规例|条例|政治|执政|政府|政委|国务院|人大|人民代表大会|中央|国家主席|总书记|国家领导人|总理|改革|整改|整治|规管|监管|财政|税|人民银行|央行|赤字|利率'
    
economic_mask = df['text'].fillna('').str.contains(economic_pattern)
policy_mask = df['text'].fillna('').str.contains(policy_pattern)
uncertainty_mask = df['text'].fillna('').str.contains(uncertainty_pattern)


print('enconomic新闻条数: ', economic_mask.sum())
print('policy新闻条数: ', policy_mask.sum())
print('uncertainty新闻条数: ', uncertainty_mask.sum())

print()

print('enconomic&policy同时出现条数: ', (economic_mask & policy_mask).sum())
print('enconomic&policy&uncertainty同时出现条数: ', (economic_mask & policy_mask & uncertainty_mask).sum())

Run

enconomic新闻条数:  617182
policy新闻条数:  1246681
uncertainty新闻条数:  56358

enconomic&policy同时出现条数:  510791
enconomic&policy&uncertainty同时出现条数:  34332

2.5 相关内容

用到以上操作的代码,通过本文以及这4个推文,巩固 pandas 操作知识点。



三、EPU计算函数

有了以上基本知识,就可以使用大邓设计的 cal_epu_index 函数,该函数可针对任意一个新闻数据(csv格式) 计算 EPU 。

需要注意, 因为是对一个媒体进行计算,所以没有进行标准化和归一化。

所以媒体1、媒体2计算得到的两个 epu1epu2 进行数值大小的比较是没有意义的。 如果你有多个媒体,计算得到多个 epu1epu2epu3, 想计算 mean_epu , 那么记得实现论文算法里的 step4, 再执行 step5 求均值。

def cal_epu_index(csvf, freq='M'):
    """
    csvf  新闻csv文件的位置, 含date和text两个字段,每行是一条新闻
    freq  epu的粒度, 年Y、月M、日D
    
    #economic、uncertainty、policy整理自
    #Huang, Yun, and Paul Luk. "Measuring economic policy uncertainty in China." China Economic Review 59 (2020): 101367
    
    返回dataFrame, 含字段date和epu
    """
    import pandas as pd
    economic_pattern = '经济|金融'
    uncertainty_pattern = '不确定|不明确|波动|震荡|动荡|不稳|未明|不明朗|不清晰|未清晰|难料|难以预料|难以预测|难以预计|难以估计|无法预料|无法预测|无法预计|无法估计|不可预料|不可预测|不可预计|不可估计'
    policy_pattern = '政策|制度|体制|战略|措施|规章|规例|条例|政治|执政|政府|政委|国务院|人大|人民代表大会|中央|国家主席|总书记|国家领导人|总理|改革|整改|整治|规管|监管|财政|税|人民银行|央行|赤字|利率'
    
    df = pd.read_csv(csvf, compression='gzip')
    df['date'] = pd.to_datetime(df['date'])
    
    datas = []
    for date, period_df in df.groupby(pd.Grouper(key='date', freq=freq)):
        data = dict()
        data['date'] = date #month是datetime型日期,一般为每个月的最后一日
        economic_mask = period_df['text'].fillna('').str.contains(economic_pattern)
        policy_mask = period_df['text'].fillna('').str.contains(policy_pattern)
        uncertainty_mask = period_df['text'].fillna('').str.contains(uncertainty_pattern)

        #在出现经济词的新闻中,统计出现政策、不确定新的比例
        data['epu'] = (economic_mask & policy_mask & uncertainty_mask).sum() / economic_mask.sum()
        datas.append(data)
    raw_epu_df = pd.DataFrame(datas)
    return raw_epu_df

#人民日报
rmrb_EPU_df = cal_epu_index(csvf='rmrb.csv.gzip', freq='M')

#保存结果
#rmrb_EPU_df.to_csv('rmrb_epu.csv', index=False)
rmrb_EPU_df


gmrb_EPU_df = cal_epu_index(csvf='gmrb.csv.gzip', freq='M')

#保存结果
#gmrb_EPU_df.to_csv('gmrb_epu.csv', index=False)
gmrb_EPU_df



四、可视化

4.1 df.plot

df.plot使用的前提是要将日期字段调整为index, 满足下面形态的数据可以使用.plot绘图

rmrb_EPU_df.set_index('date')


rmrb_EPU_df.set_index('date').plot(figsize=(10, 5), title='EPU Index \nsource: China Renmin Daily News')


4.2 支持中文

支持中文的代码,无脑copy

import matplotlib.pyplot as plt
import matplotlib
import scienceplots
import platform
import matplotlib_inline
matplotlib_inline.backend_inline.set_matplotlib_formats('png', 'svg')

plt.style.use(['science', 'no-latex', 'cjk-sc-font'])
system = platform.system()  # 获取操作系统类型
if system == 'Windows':
    font = {'family': 'SimHei'}
elif system == 'Darwin':
    font = {'family': 'Arial Unicode MS'}
else:
    font = {'family': 'sans-serif'}
matplotlib.rc('font', **font)  # 设置全局字体



rmrb_EPU_df.set_index('date').plot(figsize=(10, 5))
plt.title('经济政策不确定性EPU \nsource: 人民日报', size=15)
plt.xticks(size=12)
plt.xlabel('年份', size=13)
plt.ylabel('EPU值', size=13)
plt.show()


4.3 比较两个媒体的走势

两个新闻媒体覆盖的时间段不同,

人民日报rmrb:       1946-05-15 ~ 2023-12-18
光明日报gmrb:       1985-01-01 ~ 2023-12-18

截取1985-01-01之后的数据,进行比较。

rmrb_EPU_df2 = rmrb_EPU_df[rmrb_EPU_df['date']>'1985-01-01']
gmrb_EPU_df2 = gmrb_EPU_df[gmrb_EPU_df['date']>'1985-01-01']


rmrb_EPU_df2.set_index('date').plot(figsize=(10, 5))
plt.title('月度经济政策不确定性EPU \nsource: 人民日报', size=15)
plt.xticks(size=12)
plt.xlabel('年份', size=13)
plt.ylabel('EPU值', size=13)
plt.show()


gmrb_EPU_df2.set_index('date').plot(figsize=(10, 5))
plt.title('月度经济政策不确定性EPU \nsource: 光明日报', size=15)
plt.xticks(size=12)
plt.xlabel('年份', size=13)
plt.ylabel('EPU值', size=13)
plt.show()

光明日报数据中缺失了1989年了,所以图中有空挡。但是从两个图中可以看到 epu 的走势大致一致。

作为事后诸葛的大邓, 从人民日报和光明日报计算出的EPU可以看到, 23年不应该投资,应该保守点。

嗯嗯, 同时作为投资小白,人群中的反向指标人,今年本人收益率-20%,大家开心不~


五、获取资料

点击下载本文代码epu.ipynb

点击下载rmrb_epu.csv

点击购买 新闻数据集 | 含 人民日报/经济日报/光明日报 等 7 家媒体(2023.12.18)



广而告之