使用TF-IDF提取警告说明关键词

R
jiebaR
tidytext
Author

Rui

Published

May 12, 2023

读取 pdf 文件

Code
library(pdftools)
library(tidyverse)
library(stringr)
library(tidytext)
library(jiebaR)
Code
# 读取PDF文件并将其拆分成行
raw_data <- pdf_text("text/警告汇总.pdf") %>% paste(collapse = "")

文本划分

以企业名称划分:

Code
raw_data <- raw_data %>% 
  str_split("\\d+\\s++\\s") %>% 
  unlist()
Code
raw_data <- raw_data[-1] # 正好是777家企业
raw_data <- raw_data %>% str_remove_all("\n")

查看:

Code
raw_data[1:2]
## [1] "华汇工程设计集团股份有限公司(913306007210107199), 33069615, 盛红美, 0575-88208121没有警告提示信息。 "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       
## [2] "浙江工越信息科技有限公司(913306027731278557), 33069606, 陶亚芳, 136267592631.警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05_0)的3%,本年应付职工薪酬(QC51)与营业收入(QC05_0)的比例过高或过低。请确认本年应付职工薪酬(QC51)和营业收入(QC05_0)填报是否正确!若事实如此,请予以说明。数据:本年应付职工薪酬(本年贷方累计发生额)(qc51):859 千元,营业收入(qc05_0):2730 千元说明:因研发人员工资提高,因此数据为营业收入的31%。2.警告:“平衡关系508”中,“企业核心技术所属《国家重点支持的高新技术领域》(qb16_1)”与上年数据不同,请对变化原因予以说明。数据:企业核心技术所属国家重点支持的高新技术领域(qb16_1):050303 ,pre2021qb16_1:010107说明:按照企业核心技术所属国家重点支持的高新技术领域更新填报 "

删除第一个字符串到“.”之间的所有字符串:

Code
raw_data <- raw_data %>% str_remove_all("^[^.]*\\.")
Code
raw_data[1:2]
## [1] "华汇工程设计集团股份有限公司(913306007210107199), 33069615, 盛红美, 0575-88208121没有警告提示信息。 "                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    
## [2] "警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05_0)的3%,本年应付职工薪酬(QC51)与营业收入(QC05_0)的比例过高或过低。请确认本年应付职工薪酬(QC51)和营业收入(QC05_0)填报是否正确!若事实如此,请予以说明。数据:本年应付职工薪酬(本年贷方累计发生额)(qc51):859 千元,营业收入(qc05_0):2730 千元说明:因研发人员工资提高,因此数据为营业收入的31%。2.警告:“平衡关系508”中,“企业核心技术所属《国家重点支持的高新技术领域》(qb16_1)”与上年数据不同,请对变化原因予以说明。数据:企业核心技术所属国家重点支持的高新技术领域(qb16_1):050303 ,pre2021qb16_1:010107说明:按照企业核心技术所属国家重点支持的高新技术领域更新填报 "

777家企业中,有部分企业没有警告;有些企业不止一个警告。要将带有“没有警告提示信息”的字符串剔除出去。

Code
raw_data <- raw_data[!str_detect(raw_data, "没有警告提示信息")]

保险起见,再将包含“警告”或“错误”的字符串提取出来:

Code
raw_data <- raw_data[str_detect(raw_data, "(警告:)|(错误:)")]

包含“错误”的记录有几条?

Code
raw_data %>% str_count("错误:") %>% sum()
## [1] 1

将诸如“2.警告”等字符替换为“警告”;将诸如“2.错误”等字符串替换为“错误”:

Code
raw_data <- raw_data %>% str_replace_all("\\d+\\.+警告", "警告")
raw_data <- raw_data %>% str_replace_all("\\d+\\.+错误", "错误")
Code
data <- raw_data %>% 
  paste(collapse = "") %>%
  str_split("(?=警告:)|(?=错误:)|(?=数据:)|(?=说明:)") %>% # ?=..用于防止去除分隔符
  unlist()
Code
data <- data[data != ""]

提取警告和说明

Code
jinggao <- data[str_detect(data, "(警告:)|(错误:)")]
shuju <- data[str_detect(data, "数据:")]
shuoming <- data[str_detect(data, "说明:")]

对于“警告”:

Code
uni_jinggao <- unique(jinggao)
num_jinggao <- jinggao %>% table() %>% as.data.frame()
colnames(num_jinggao) <- c("警告与错误", "出现次数")
num_jinggao <- num_jinggao %>% arrange(desc(出现次数))
Code
num_jinggao %>% head(3) %>% knitr::kable()
警告与错误 出现次数
警告:“第3步 (二)经济概况”中,正常情况下,营业利润(QC120)应该为正数!如为负值请附上原因说明。 293
警告:“第3步 (二)经济概况”中,一般情况下,利润总额应大于或等于0,如果为负数,请予以说明。 248
警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05_0)的3%,本年应付职工薪酬(QC51)与营业收入(QC05_0)的比例过高或过低。请确认本年应付职工薪酬(QC51)和营业收入(QC05_0)填报是否正确!若事实如此,请予以说明。 187
Code
write_excel_csv(num_jinggao, "output/警告类型汇总.csv")

警告与说明合并

Note

要注意:一种警告往往对应着多种说明。可以使用 group_by 对警告进行汇总,每种警告对应的多个说明合并起来。这样,每种警告就像是文章标题,对应的说明合集就像是文章的内容。然后可以计算 TF-IDF 来提取“每篇文章”的特征(关键词)。

Code
raw_text <- data.frame("警告" = jinggao, "说明" = shuoming)

剔除“说明:”冗余词汇,因为列名已经包含该信息了:

Code
raw_text <- raw_text %>%
  mutate(说明 = str_remove(说明, "说明:"))

使用 jiebaR 进行分词

按照警告一列对说明进行汇总:

Code
text <- raw_text %>%
  group_by(警告) %>%
  summarise(说明汇总 = paste(说明, collapse = ";")) %>%
  ungroup()

加载分词引擎,设置停用词

Code
# 加载分词引擎,并配置停止词过滤
work <- worker(stop_word = "text/cn_stopwords.txt")

对于单个“文章”

Code
text$说明汇总[1] %>% segment(work)
## [1] "我司" "政府" "补贴" "额外" "收益"

对所有“文章”

创建 tokens 列表:

Code
token_list <- list()
for (i in 1:nrow(text)) {
  tok <- text$说明汇总[i] %>%
    str_replace_all("[0-9, a-z, A-Z]", "") %>% # 去除阿拉伯数字、英文字母
    segment(work)
  
  temp <- data.frame("book" = rep(text$警告[i], length(tok)),
                     "word" = tok)
  
  token_list[[i]] <- temp
}

合并 tokens 列表为数据框:

Code
tokens_df <- token_list %>% purrr::map_dfr(~.)

使用 tidytext 计算 TF-IDF

关于 TF-IDF 的相关解释可以参见这篇博客:http://www.ruanyifeng.com/blog/2013/03/tf-idf.html。写得非常棒!

Warning

TF-IDF 有一个缺点:容易把一些生僻词、人名、地名当作文章的关键词。从本篇文章的结果多少可以看出这个缺点。处理方法是在分词时将这些词剔除,或者在提取关键词后进行人工检查。

Code
tokens_df <- tokens_df %>%
  count(book, word, sort = TRUE)
Code
total_words <- tokens_df %>%
  group_by(book) %>%
  summarize(total = sum(n))
Code
tokens_df <- tokens_df %>%
  left_join(total_words)
Code
tokens_df %>% head(3) %>% knitr::kable()
book word n total
警告:“第3步 (二)经济概况”中,正常情况下,营业利润(QC120)应该为正数!如为负值请附上原因说明。 公司 154 2795
警告:“第3步 (二)经济概况”中,一般情况下,利润总额应大于或等于0,如果为负数,请予以说明。 公司 136 2407
警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05_0)的3%,本年应付职工薪酬(QC51)与营业收入(QC05_0)的比例过高或过低。请确认本年应付职工薪酬(QC51)和营业收入(QC05_0)填报是否正确!若事实如此,请予以说明。 公司 105 2050
Code
tokens_df <- tokens_df %>%
 bind_tf_idf(word, book, n)
tokens_df %>% head(3) %>% knitr::kable()
book word n total tf idf tf_idf
警告:“第3步 (二)经济概况”中,正常情况下,营业利润(QC120)应该为正数!如为负值请附上原因说明。 公司 154 2795 0.0550984 0.3586338 0.0197601
警告:“第3步 (二)经济概况”中,一般情况下,利润总额应大于或等于0,如果为负数,请予以说明。 公司 136 2407 0.0565019 0.3586338 0.0202635
警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05_0)的3%,本年应付职工薪酬(QC51)与营业收入(QC05_0)的比例过高或过低。请确认本年应付职工薪酬(QC51)和营业收入(QC05_0)填报是否正确!若事实如此,请予以说明。 公司 105 2050 0.0512195 0.3586338 0.0183690
Code
TF_IDF <- tokens_df %>% 
  select(book, word, n, tf_idf) %>%
  arrange(desc(book), desc(tf_idf))
Note

注意 desc() 只接受一个列名,想要按照多列进行降序排序,应该为 arrange(desc(book), desc(tf_idf)),而不是 arrange(desc(book, tf_idf))

Code
TF_IDF <- TF_IDF %>% 
  left_join(num_jinggao, by = c("book" = "警告与错误")) %>%
  rename("book_freq" = "出现次数")

TF_IDF %>% head(3) %>% knitr::kable()
book word n tf_idf book_freq
错误:“第4步 (二)经济概况续”中,“其中:本年折旧(qc61)”必须大于或等于0(不能为负数)。 处置 1 0.8329000 1
错误:“第4步 (二)经济概况续”中,“其中:本年折旧(qc61)”必须大于或等于0(不能为负数)。 固定资产 1 0.7370060 1
错误:“第4步 (二)经济概况续”中,“其中:本年折旧(qc61)”必须大于或等于0(不能为负数)。 1 0.1764198 1

整理汇总:

Code
TF_IDF %>% select(-tf_idf) %>%
  group_by(book) %>%
  summarise(word = paste(word, collapse = ";")) %>%
  ungroup() %>%
  rename("警告" = "book", "说明关键词" = "word") %>%
  write_excel_csv("output/警告说明关键词提取结果.csv")

筛选出 TF-IDF 较高的 tokens

Code
top5 <- TF_IDF %>% 
  group_by(book) %>%
  arrange(desc(tf_idf)) %>%
  slice_head(n = 5)

检查一下警告种类:

Code
top5$book %>% unique() %>% length()
## [1] 73
Code
main_shuoming <- list()

i <- 1
for (j in uni_jinggao) {
  str <- top5 %>% filter(book == j) %>% "$"(word) %>% paste(collapse = "|")
  
  zongjie <- raw_text %>% filter(警告 == j) %>% "$"(说明)
  idx <- zongjie %>% str_detect(str)
  
  zongjie <- zongjie[idx]
  
  temp <- data.frame("警告" = rep(j, length(zongjie)), "主要说明" = zongjie)
  
  main_shuoming[[i]] <- temp
  
  i <- i + 1
}

列表转换为数据框:

Code
main_shuoming <- main_shuoming %>% purrr::map_dfr(~.)
str(main_shuoming)
## 'data.frame':    1415 obs. of  2 variables:
##  $ 警告    : chr  "警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05"| __truncated__ "警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05"| __truncated__ "警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05"| __truncated__ "警告:“平衡关系138”中,本年应付职工薪酬(QC51)超过营业收入(QC05_0)的30%或本年应付职工薪酬(QC51)小于营业收入(QC05"| __truncated__ ...
##  $ 主要说明: chr  "因研发人员工资提高,因此数据为营业收入的31%。" "人员成本上涨,本企业主要为人员成本" "我公司属于软件开发行业,主要支出用于员工工资 " "销售少,经营收入少。员工工资照常发放 " ...
Code
out <- top5 %>%
  group_by(book) %>%
  summarise(说明关键词 = paste(word, collapse = ";"),
            警告出现频率 = as.integer(mean(book_freq))) %>%
  ungroup() %>%
  rename("警告" = "book") %>%
  right_join(main_shuoming, by = "警告") %>%
  select(警告, 警告出现频率, everything())

out %>% head(3) %>% knitr::kable()
警告 警告出现频率 说明关键词 主要说明
警告:“平衡关系104”中,其他收益(QC232)超过1亿元,请确认是否为录入错误!若事实如此,请予以说明。 1 额外;补贴;收益;政府;我司 因我司有政府补贴等额外收益
警告:“平衡关系108”中,请确认净利润(QC12)是否漏填,如果确实为0,则请按实际情况予以说明。 1 不好;盈利;销售;去年;单位 我单位去年销售不好,没有盈利。
警告:“平衡关系110”中,净利润(QC12)超过营业收入(QC05_0)的30%,请确认净利润(QC12)和营业收入(QC05_0)填报是否正确!若事实如此,请予以说明。 31 净利润;利润;营业;收入;较高 公司产品市场收益不错,同时有部分营业外收入,净利润相对客观,情况属实。