名师讲堂|突破性创新与颠覆性创新:使用BERT和 SBERT 模型计算专利文本相似度(二)

上次课程中我们计算了上市公司专利与其引用专利的文本相似度。今天我们继续上次课的内容,筛选颠覆性创新专利。

「企业数字化转型与颠覆性技术创新——来自专利网络与SBERT模型的微观证据_黄先海」文献中,作者定义专利的创新度为其与所有引用专利相似度平均值的倒数(取对数):

也就是和引用专利的相似度越低,创新度越高。

然后定义突破性技术创新的专利为:同年同一行业企业的专利中,创新度位于前 20% 的专利。重要影响力技术创新的专利为:同年同一行业企业的专利中,3 年内被引次数排名前 20% 的专利。

同时满足上述两个条件的专利即位具有颠覆性创新的。

在处理前仅保留发明专利并且删除金融和保险类企业。

计算专利创新度

因此我们首先计算专利的创新度。

2021年上市公司专利与其引用专利的相似度计算结果.dta 文件是按照上次课程内容计算的结果:

library(tidyverse)
haven::read_dta("2021年上市公司专利与其引用专利的相似度计算结果.dta") %>%
distinct(newipzlid, newipzlid2, .keep_all = T) -> dfall

dfall

#> # A tibble: 1,398 × 4
#> 股票代码 newipzlid newipzlid2 similarity
#> <chr> <dbl> <dbl> <dbl>
#> 1 600028 2001001259 199980468 0.724
#> 2 600019 2001003922 199817092 0.773
#> 3 000537 2001004017 199251691 0.563
#> 4 000537 2001004017 2000118896 0.400
#> 5 000537 2001004017 2000103920 0.439
#> 6 000537 2001004017 199202422 0.560
#> 7 600089 2001004068 199451721 0.930
#> 8 600089 2001004068 199844765 0.828
#> 9 600716 2001004964 199739276 0.802
#> 10 603051 2001004971 198611343 0.722
#> # ℹ 1,388 more rows

其实在上次课程中的计算过程中可以先对“专利对”进行去重后再计算,可以节省很多运算量。

匹配上市公司信息:

read_rds("2001年上市公司专利引用信息.rds") -> df0

df0 %>%
select(股票代码, newipzlid, newipzlid2) %>%
left_join(dfall) -> dfall

dfall

#> # A tibble: 1,422 × 4
#> 股票代码 newipzlid newipzlid2 similarity
#> <chr> <dbl> <dbl> <dbl>
#> 1 600028 2001001259 199980468 0.724
#> 2 600019 2001003922 199817092 0.773
#> 3 000537 2001004017 199251691 0.563
#> 4 000537 2001004017 2000118896 0.400
#> 5 000537 2001004017 2000103920 0.439
#> 6 000537 2001004017 199202422 0.560
#> 7 600089 2001004068 199451721 0.930
#> 8 600089 2001004068 199844765 0.828
#> 9 600716 2001004964 199739276 0.802
#> 10 603051 2001004971 198611343 0.722
#> # ℹ 1,412 more rows

根据创新度公式计算每个专利的创新度:

dfall %>%
group_by(股票代码, newipzlid) %>%
summarise(Innov = log(n() / sum(similarity))) %>%
ungroup() -> innovdf

innovdf

#> # A tibble: 686 × 3
#> 股票代码 newipzlid Innov
#> <chr> <dbl> <dbl>
#> 1 000026 2001026802 0.369
#> 2 000063 2001026367 0.264
#> 3 000063 2001026374 0.307
#> 4 000063 2001026405 0.269
#> 5 000063 2001026406 0.357
#> 6 000063 2001026422 0.998
#> 7 000063 2001026458 0.0497
#> 8 000063 2001026511 0.290
#> 9 000063 2001026535 0.622
#> 10 000063 2001026536 0.352
#> # ℹ 676 more rows

不过暂时我们还不能直接继续后面的步骤,因为这里没有涵盖全部的,也就是没有引用的专利不在其中,这些专利的创新度其实就是 0。

读取全部 2021 年上市公司发明专利

readr::read_csv("2001年上市公司与发明专利数据匹配结果.csv") -> df0a

df0a

#> # A tibble: 2,213 × 10
#> 股票代码 股票名称 newipzlid 年份 申请日 标题 摘要 公开公告号 专利类型
#> <chr> <chr> <dbl> <dbl> <date> <chr> <chr> <chr> <chr>
#> 1 002215 诺普信 2001000048 2001 2001-01-05 阿维菌素… 一种杀虫… CN1299592A 发明申请
#> 2 002215 诺普信 2001000049 2001 2001-01-05 阿维菌素… 一种杀虫… CN1299593A 发明申请
#> 3 002215 诺普信 2001000050 2001 2001-01-05 吡虫啉微… 吡虫啉微… CN1299594A 发明申请
#> 4 002215 诺普信 2001000051 2001 2001-01-05 阿维菌素… 一种杀虫… CN1299595A 发明申请
#> 5 002215 诺普信 2001000052 2001 2001-01-05 吡虫啉·… 本发明为… CN1299596A 发明申请
#> 6 600481 双良节能 2001000235 2001 2001-01-13 相变技术… 本发明涉… CN1302991A 发明申请
#> 7 000537 广宇发展 2001000265 2001 2001-02-26 无线移动… 本发明属… CN1303183A 发明申请
#> 8 600853 龙建股份 2001000356 2001 2001-02-19 高强度、… 本发明涉… CN1305020A 发明申请
#> 9 002215 诺普信 2001000445 2001 2001-01-05 阿维菌素… 一种杀虫… CN1305711A 发明申请
#> 10 600005 武钢股份 2001000491 2001 2001-02-28 高性能管… 本发明涉… CN1305879A 发明申请
#> # ℹ 2,203 more rows
#> # ℹ 1 more variable: 授权公告日 <date>

在专利数据的介绍里面我们提到了,专利数据里面有重复的,一种去重方法就是去除公开公告号结尾的字母之后再去重。不过这里我们不能直接去重,而是要优先保留 innovdf 里面的专利:

# 去重,但是保留 innovdf 中的结果
df0a %>%
left_join(innovdf %>% select(newipzlid) %>%
distinct() %>% mutate(class = 1)) %>%
mutate(class = if_else(is.na(class), 0, class)) %>%
mutate(公开公告号 = str_remove(公开公告号, "[A-Z]$")) %>%
group_by(股票代码, 公开公告号) %>%
filter(class == max(class, na.rm = T)) %>%
select(-class) %>%
ungroup() %>%
distinct(股票代码, 公开公告号, .keep_all = T) -> df0b

df0b

#> # A tibble: 2,203 × 10
#> 股票代码 股票名称 newipzlid 年份 申请日 标题 摘要 公开公告号 专利类型
#> <chr> <chr> <dbl> <dbl> <date> <chr> <chr> <chr> <chr>
#> 1 002215 诺普信 2001000048 2001 2001-01-05 阿维菌素… 一种杀虫… CN1299592 发明申请
#> 2 002215 诺普信 2001000049 2001 2001-01-05 阿维菌素… 一种杀虫… CN1299593 发明申请
#> 3 002215 诺普信 2001000050 2001 2001-01-05 吡虫啉微… 吡虫啉微… CN1299594 发明申请
#> 4 002215 诺普信 2001000051 2001 2001-01-05 阿维菌素… 一种杀虫… CN1299595 发明申请
#> 5 002215 诺普信 2001000052 2001 2001-01-05 吡虫啉·… 本发明为… CN1299596 发明申请
#> 6 600481 双良节能 2001000235 2001 2001-01-13 相变技术… 本发明涉… CN1302991 发明申请
#> 7 000537 广宇发展 2001000265 2001 2001-02-26 无线移动… 本发明属… CN1303183 发明申请
#> 8 600853 龙建股份 2001000356 2001 2001-02-19 高强度、… 本发明涉… CN1305020 发明申请
#> 9 002215 诺普信 2001000445 2001 2001-01-05 阿维菌素… 一种杀虫… CN1305711 发明申请
#> 10 600005 武钢股份 2001000491 2001 2001-02-28 高性能管… 本发明涉… CN1305879 发明申请
#> # ℹ 2,193 more rows
#> # ℹ 1 more variable: 授权公告日 <date>

筛选突破性技术创新专利

同年同一行业企业的专利中,创新度位于前 20% 的专利。

因此这里我们还需要上市公司的行业信息。这里我们使用证监会行业分类,这个变量在之前分享的这个数据里面有:

2000~2024 年上市公司注册地址与办公地址(含经纬度及其所处的省市区县):https://rstata.duanshu.com/#/brief/course/746d4f595eba41a2a116b5a6edc3ac0a

IndustryName [行业名称] - 2023年(含)之后的年份是按照中国上市公司协会行业分类, 2012年(含)之后的年份是按照2012版证监会行业分类,2012年之前是按照2001版证监会行业分类。如:2012年证监会行业分类是根据统计截止日期与证监会2012版本披露的实施日期,用2012版本的行业填充。

附件中提供了 2001 年的数据:

haven::read_dta("2001年上市公司行业信息.dta") -> df1

df1 %>%
select(股票代码, 行业名称, 年份) %>%
distinct() -> industrydf

industrydf

#> # A tibble: 1,166 × 3
#> 股票代码 行业名称 年份
#> <chr> <chr> <dbl>
#> 1 000001 其他商业银行 2001
#> 2 000002 房地产开发与经营业 2001
#> 3 000003 综合类 2001
#> 4 000004 公路运输业 2001
#> 5 000005 综合类 2001
#> 6 000006 房地产开发与经营业 2001
#> 7 000007 房地产开发与经营业 2001
#> 8 000008 计算机应用服务业 2001
#> 9 000009 综合类 2001
#> 10 000010 商业经纪与代理业 2001
#> # ℹ 1,156 more rows

df1 %>%
select(股票代码, 行业名称2 = 行业名称) %>%
distinct(股票代码, .keep_all = T) -> industrydf2

industrydf2

#> # A tibble: 1,166 × 2
#> 股票代码 行业名称2
#> <chr> <chr>
#> 1 000001 其他商业银行
#> 2 000002 房地产开发与经营业
#> 3 000003 综合类
#> 4 000004 公路运输业
#> 5 000005 综合类
#> 6 000006 房地产开发与经营业
#> 7 000007 房地产开发与经营业
#> 8 000008 计算机应用服务业
#> 9 000009 综合类
#> 10 000010 商业经纪与代理业
#> # ℹ 1,156 more rows

如果跨年处理的话,这里其实会遇到同一家公司在不同年份的行业不同。所以我准备了两个行业数据,一个是各年各公司的行业信息,另外一个则是每个公司的行业(不分行业,粗暴的去重),这样如果和第一个匹配成功的就用第一个的结果,第一个没有匹配成功的用第二个的结果。

df0b %>%
select(股票代码, newipzlid) %>%
left_join(innovdf) %>%
mutate(Innov = if_else(is.na(Innov), 0, Innov),
年份 = as.numeric(str_sub(format(newipzlid, scientific = FALSE, trim = TRUE), 1, 4))) %>%
filter(Innov > 0) %>%
left_join(industrydf) %>%
left_join(industrydf2) %>%
mutate(行业名称 = if_else(is.na(行业名称), 行业名称2, 行业名称)) %>%
filter(!is.na(行业名称)) -> df2a

df2a

#> # A tibble: 493 × 6
#> 股票代码 newipzlid Innov 年份 行业名称 行业名称2
#> <chr> <dbl> <dbl> <dbl> <chr> <chr>
#> 1 600028 2001001259 0.323 2001 天然原油开采业 天然原油开采业
#> 2 600019 2001003922 0.257 2001 炼钢业 炼钢业
#> 3 000537 2001004017 0.712 2001 综合类 综合类
#> 4 600089 2001004068 0.129 2001 电工器械制造业 电工器械制造业
#> 5 600716 2001004964 0.220 2001 非金属矿物制品业 非金属矿物制品业
#> 6 600019 2001006006 0.0291 2001 炼钢业 炼钢业
#> 7 600019 2001008425 0.164 2001 炼钢业 炼钢业
#> 8 000527 2001009690 1.64 2001 日用电器制造业 日用电器制造业
#> 9 600871 2001014526 0.352 2001 合成纤维制造业 合成纤维制造业
#> 10 000939 2001020999 0.468 2001 电力生产业 电力生产业
#> # ℹ 483 more rows

可以看到这里我又把创新度为 0 的删除了。这其实是因为最开始我准备用所有的专利数据计算 80% 分位数进行筛选,但是后来发现创新度为 0 的太多,基于所有专利技术的 80% 分位数不能有效的筛选,所以到这里我还是把创新度为 0 的删除了。

计算分位数:

df2a %>%
select(-行业名称2) %>%
group_by(年份, 行业名称) %>%
summarise(p80 = quantile(Innov, probs = 0.8)) %>%
ungroup() -> df2b

df2a %>%
select(-行业名称2) %>%
left_join(df2b) %>%
filter(Innov >= p80) -> df2c

df2c

#> # A tibble: 112 × 6
#> 股票代码 newipzlid Innov 年份 行业名称 p80
#> <chr> <dbl> <dbl> <dbl> <chr> <dbl>
#> 1 000537 2001004017 0.712 2001 综合类 0.706
#> 2 600089 2001004068 0.129 2001 电工器械制造业 0.129
#> 3 000527 2001009690 1.64 2001 日用电器制造业 0.642
#> 4 000063 2001026422 0.998 2001 通信设备制造业 0.603
#> 5 000063 2001026535 0.622 2001 通信设备制造业 0.603
#> 6 000063 2001026650 0.671 2001 通信设备制造业 0.603
#> 7 000063 2001026674 0.748 2001 通信设备制造业 0.603
#> 8 000063 2001026723 0.714 2001 通信设备制造业 0.603
#> 9 000837 2001026734 0.513 2001 金属加工机械制造业 0.513
#> 10 000026 2001026802 0.369 2001 机械、设备、仪表 0.369
#> # ℹ 102 more rows

这些专利就是具有突破性技术创新的专利了。

重要影响力技术创新专利

同年同一行业企业的专利中,3 年内被引次数排名前 20% 的专利。

这里需要使用每个专利在特定窗口内的被引用次数,之前平台上有分享过这个数据:

1985~2024 年各专利当年~十一年内的被自引、被他引数量统计:https://rstata.duanshu.com/#/brief/course/51e62d1eae074a4d8174a3969f5025c6

附件中提供了 2001 年的部分数据:

haven::read_dta("2021年各专利当年~十一年内的被自引、被他引数量统计.dta") -> df3
df3

#> # A tibble: 46,648 × 14
#> year newipzlid 引用或被引用 当年 两年内 三年内 四年内 五年内 六年内 七年内
#> <dbl> <chr> <chr> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
#> 1 2001 2001000001 被他引信息 0 1 1 1 1 1 1
#> 2 2001 2001000003 被他引信息 0 2 2 2 2 2 2
#> 3 2001 2001000005 被他引信息 0 0 0 0 0 0 0
#> 4 2001 2001000006 被他引信息 0 0 1 1 1 1 1
#> 5 2001 2001000007 被他引信息 0 0 0 0 0 0 0
#> 6 2001 2001000008 被他引信息 0 0 0 0 0 0 0
#> 7 2001 2001000009 被他引信息 0 0 0 0 0 1 2
#> 8 2001 2001000012 被他引信息 0 0 0 0 0 0 0
#> 9 2001 2001000013 被他引信息 0 0 2 2 2 2 4
#> 10 2001 2001000016 被他引信息 0 0 0 0 0 0 0
#> # ℹ 46,638 more rows
#> # ℹ 4 more variables: 八年内 <dbl>, 九年内 <dbl>, 十年内 <dbl>, 十一年内 <dbl>

总被引次数 = 总被自引次数 + 总被他引次数,计算每个专利的总被引次数:

df3 %>%
select(newipzlid, 三年内) %>%
count(newipzlid, wt = 三年内) %>%
rename(三年内被引次数 = n) %>%
mutate(newipzlid = as.numeric(newipzlid)) -> df3a

df3a

#> # A tibble: 42,420 × 2
#> newipzlid 三年内被引次数
#> <dbl> <dbl>
#> 1 2001000001 1
#> 2 2001000003 2
#> 3 2001000005 0
#> 4 2001000006 1
#> 5 2001000007 0
#> 6 2001000008 0
#> 7 2001000009 0
#> 8 2001000012 0
#> 9 2001000013 2
#> 10 2001000016 0
#> # ℹ 42,410 more rows

分年分行业计算被引用次数的 80% 分位数:

df0b %>%
select(股票代码, newipzlid, 年份) %>%
left_join(df3a) %>%
filter(三年内被引次数 > 0, !is.na(三年内被引次数)) %>%
left_join(industrydf) %>%
left_join(industrydf2) %>%
mutate(行业名称 = if_else(is.na(行业名称), 行业名称2, 行业名称)) %>%
filter(!is.na(行业名称)) %>%
select(-行业名称2) %>%
group_by(年份, 行业名称) %>%
summarise(p80b = quantile(三年内被引次数, probs = 0.8)) %>%
ungroup() -> df3b

df3b

#> # A tibble: 27 × 3
#> 年份 行业名称 p80b
#> <dbl> <chr> <dbl>
#> 1 2001 中药材及中成药加工业 1
#> 2 2001 其他专用设备制造业 6
#> 3 2001 其他制造业 1
#> 4 2001 化学农药制造业 1
#> 5 2001 化学原料及化学制品制造业 3
#> 6 2001 化学肥料制造业 1.8
#> 7 2001 化学药品制剂制造业 4.6
#> 8 2001 原油加工业 1
#> 9 2001 合成纤维制造业 5.6
#> 10 2001 基本化学原料制造业 4
#> # ℹ 17 more rows

然后再筛选:

df0 %>%
select(股票代码, newipzlid, 年份) %>%
left_join(df3a) %>%
filter(三年内被引次数 > 0, !is.na(三年内被引次数)) %>%
left_join(industrydf) %>%
left_join(industrydf2) %>%
mutate(行业名称 = if_else(is.na(行业名称), 行业名称2, 行业名称)) %>%
filter(!is.na(行业名称)) %>%
select(-行业名称2) %>%
left_join(df3b) %>%
filter(三年内被引次数 >= p80b) -> df3c

df3c

#> # A tibble: 57 × 6
#> 股票代码 newipzlid 年份 三年内被引次数 行业名称 p80b
#> <chr> <dbl> <dbl> <dbl> <chr> <dbl>
#> 1 000537 2001004017 2001 1 综合类 1
#> 2 000537 2001004017 2001 1 综合类 1
#> 3 000537 2001004017 2001 1 综合类 1
#> 4 000537 2001004017 2001 1 综合类 1
#> 5 600019 2001006006 2001 3 炼钢业 3
#> 6 600019 2001006006 2001 3 炼钢业 3
#> 7 600028 2001026483 2001 4 天然原油开采业 3
#> 8 600028 2001026483 2001 4 天然原油开采业 3
#> 9 600028 2001026483 2001 4 天然原油开采业 3
#> 10 000063 2001027334 2001 16 通信设备制造业 2.6
#> # ℹ 47 more rows

颠覆性创新专利

取上述两种筛选结果的交集即可得到颠覆性创新专利:

df2c %>%
inner_join(df3c) %>%
select(股票代码, 年份, newipzlid, 行业名称, 三年内被引次数, 创新度 = Innov, everything()) -> df4

df4 %>%
haven::write_dta("2001上市公司颠覆性创新专利筛选结果(SBERT_paraphrase-multilingual-MiniLM-L12-v2模型).dta")

再循环所有年份就可以得到全部的上市公司颠覆性创新专利了~

点击这里跳转到 RStata 短书平台获取附件:名师讲堂|突破性创新与颠覆性创新:使用BERT和 SBERT 模型计算专利文本相似度(二)

评论