名师讲堂|使用 R 语言测算专利的创新突破度:CD指数(更新)

上次课程的代码里面计算的时候忽略了“如果某个专利 j 没有引用专利 i 但是引用了其后项引用,则 cd = 0” 的情况,导致计算结果里面 1 和 -1 出现的太多。所以赶紧更正了下。

在《Nature》论文:「Papers and patents are becoming less disruptive over time」 中使用了 CD 指数数据,该指标的计算公式如下:

其中当专利 j 引用了专利 i 时 fit 取1,否则取 0;若专利 j 引用了专利 i 的后向引用专利时 bit 取 1,否则取 0;

附件中也提供了该论文的 pdf 文件。

论文中也提供了一个示意图帮助我们理解这个指数:

看起来很复杂,其实只需要关注三个:

  1. 若后面的专利既引用专利 i 又引用专利 i 的后向引用专利,那么专利 i 的该项引用的 CD1 指数 -1;
  2. 若后面的专利只引用专利 i 但未引用专利 i 的后向引用专利,那么专利i的该项引用的 CDI 指数为 1;
  3. 若后面的专利只引用专利 i 的后向引用专利,但是没有引用专利 i,那么专利 i 的该项引用的 CDI 指数为 0;

这样直接计算得到的是每个专利引用关系的 CD 指数,平均之后就得到了某个专利的 CD 指数。

今天的课程中我们将以上市公司专利为例进行讲解。

在计算之前我们需要准备两组数据:

  1. 一个是专利的引用关系,也就是每个专利引用了其他的哪些专利;
  2. 第二个是专利的属性信息,也就是每个公司每年申请了哪些专利。

这两个数据我们都分享过。全部专利的引用与被引用关系数据在这里:

1985~2024 年全部专利引用与被引用详细信息:https://rstata.duanshu.com/#/brief/course/225ad0b59a9945e1831d2e8b96ca1001;

由于全部的数据非常大,所以附件中仅仅提供了含有 newipzlid 和 newipzlid2 的数据作,这两个变量就足够用来计算专利的 CD 指数了。通过类似如下代码即可处理得到:

这部分代码就不用运行了,“全部专利引用与被引用信息分年.rds” 是一个超过 40 GB 的大数据,代码需要电脑性能足够好才能运行。

library(tidyverse)
read_rds("全部专利引用与被引用信息分年.rds") -> df1

# 根据公开公告号去重
df1 %>%
mutate(公开公告号_original = str_remove(公开公告号_original, "[A-Z]$"),
公开公告号 = str_remove(公开公告号, "[A-Z]$")) %>%
distinct(公开公告号_original, 公开公告号, .keep_all = T) -> df2

# 引用信息
bind_rows(
df2 %>%
filter(引用或被引用 %in% c("他引信息", "自引信息")) %>%
set_names(c("newipzlid1", "申请日1", "公开公告号1",
"省1", "省代码1", "市1", "市代码1", "县1", "县代码1",
"引用或被引用",
"newipzlid2", "申请日2", "公开公告号2",
"省2", "省代码2", "市2", "市代码2", "县2", "县代码2")) %>%
select(-引用或被引用),
df2 %>%
filter(引用或被引用 %in% c("被他引信息", "被自引信息")) %>%
set_names(c("newipzlid2", "申请日2", "公开公告号2",
"省2", "省代码2", "市2", "市代码2", "县2", "县代码2",
"引用或被引用",
"newipzlid1", "申请日1", "公开公告号1",
"省1", "省代码1", "市1", "市代码1", "县1", "县代码1")) %>%
select(-引用或被引用)
) %>%
distinct() -> df0a

df0a

# 保存
df0a %>%
write_rds("全部专利引用信息(去重后).rds")

# 选择所需的变量
df0a %>%
select(newipzlid = newipzlid1, newipzlid2,
date = 申请日1, date2 = 申请日2) -> patent_citations

patent_citations %>%
write_rds("patent_citations.rds")

附件中提供的“patent_citations.rds”文件夹就是根据这个代码处理得到的。

考虑专利被引用情况通常需要限定窗口,例如这里限定 5 年的引用窗口,也就是只保留引用间隔 5 年内的:

library(tidyverse)
read_rds("patent_citations.rds") -> patent_citations

patent_citations %>%
filter(date - date2 <= dyears(5) & date - date2 >= 0) -> patent_citations2

patent_citations2 %>%
write_rds("patent_citations2.rds")

patent_citations2 %>%
slice_sample(n = 10)
#> # A tibble: 10 × 4
#> newipzlid newipzlid2 date date2
#> <dbl> <dbl> <date> <date>
#> 1 20141301234 20140334274 2014-09-15 2014-04-02
#> 2 20183713037 20172740680 2018-12-25 2017-11-10
#> 3 20213287175 20201944456 2021-09-15 2020-06-06
#> 4 20211043088 20171503410 2021-04-12 2017-07-21
#> 5 20211160131 20162258019 2021-04-23 2016-10-13
#> 6 20221814582 20182316620 2022-06-02 2018-08-30
#> 7 20173363949 20160236524 2017-12-28 2016-02-15
#> 8 20191239857 20170241169 2019-05-29 2017-02-28
#> 9 20191095681 20190968608 2019-04-24 2019-04-24
#> 10 20194387760 20190298138 2019-12-31 2019-01-15

附件中也提供了 “patent_citations2.rds”,因此可以直接读取这个。

由于需要考虑这种情况:“若后面的专利只引用专利 i 的后向引用专利,但是没有引用专利 i,那么专利 i 的该项引用的 CDI 指数为 0”, 所以我们就不能使用之前旧版本课程里面的方法,因为那个方法只能获取引用专利 i 的情况。也就是我们必须得循环所有的专利来计算了,效率很低,但是没特别好的办法了。

以某个专利为例:

# 以某个专利为例:
id <- 20190809613
patent_citations2 %>%
filter(newipzlid2 == id) -> testpat

testpat
#> # A tibble: 1 × 4
#> newipzlid newipzlid2 date date2
#> <dbl> <dbl> <date> <date>
#> 1 20210978229 20190809613 2021-03-02 2019-04-22
# 所有可能的 j
# 准备 i 的后向引用
patent_citations2 %>%
filter(newipzlid == id) %>%
select(newipzlid2) %>%
pull(newipzlid2) -> backrefs

backrefs
#> numeric(0)
# 申请日期晚于 i
patent_citations2 %>%
filter(date >= testpat$date2[1]) -> part0

# 引用 i 的
part0 %>%
filter(newipzlid2 == id) %>%
select(newipzlid) %>%
distinct(newipzlid) -> part1
part1
#> # A tibble: 1 × 1
#> newipzlid
#> <dbl>
#> 1 20210978229
# 引用 i 后向引用的
part0 %>%
filter(newipzlid2 %in% backrefs) %>%
select(newipzlid) %>%
distinct(newipzlid) -> part2

part2
#> # A tibble: 0 × 1
#> # ℹ 1 variable: newipzlid <dbl>
# 由此就可以分出三种情况:
# 此处代码需要下载讲义材料查看~

这样就得到了一个专利的 CD 指数。循环所有的即可。

不过所以需要计算的专利总数超过了 4000 万,也就是需要循环四千次上面的代码,效率非常低。

我们可以进一步思考一种特殊的情况,也就是 如果某个专利 i,也就是 patent_citations2 里面的 newipzlid2 一列的专利,没有引用其他专利,那么就是说这个专利 i 没有后向引用,因此它就不存在 CD 指数为 0 或者为 -1 的情况,都是 1,平均起来也都是 1。所以这些专利就可以筛选出来不必计算了:

# # 没有后向引用的
# patent_citations2 %>%
# select(newipzlid2) %>%
# anti_join(
# patent_citations2 %>%
# select(newipzlid2 = newipzlid)
# ) %>%
# distinct(newipzlid2) -> nobackrefs
#
# nobackrefs %>%
# mutate(cd_index = 1) %>%
# write_rds("nobackrefs.rds")
#
# nobackrefs %>%
# mutate(cd_index = 1) %>%
# write_rds("nobackrefs.rds")

附件中也提供了“nobackrefs.rds”文件。

那么我们需要计算的其实只有这些:

read_rds("nobackrefs.rds") -> nobackrefs

nobackrefs
#> # A tibble: 8,277,416 × 2
#> newipzlid2 cd_index
#> <dbl> <dbl>
#> 1 19850027 1
#> 2 19857435 1
#> 3 19850937 1
#> 4 19854634 1
#> 5 19859532 1
#> 6 19850147 1
#> 7 19850143 1
#> 8 19851158 1
#> 9 19850131 1
#> 10 19851493 1
#> # ℹ 8,277,406 more rows
patent_citations2 %>%
distinct(newipzlid2) %>%
anti_join(nobackrefs) -> withbackrefs

withbackrefs
#> # A tibble: 7,028,753 × 1
#> newipzlid2
#> <dbl>
#> 1 198608410
#> 2 19850568
#> 3 19851877
#> 4 19854601
#> 5 19856362
#> 6 198601453
#> 7 198600518
#> 8 198605384
#> 9 198604263
#> 10 19857890
#> # ℹ 7,028,743 more rows

然后读取上市公司专利信息:

read_csv("2010年上市公司与专利数据匹配结果.csv") -> sspat

sspat %>%
distinct(newipzlid) %>%
filter(newipzlid %in% withbackrefs$newipzlid2) -> testmat

testmat
#> # A tibble: 19,385 × 1
#> newipzlid
#> <dbl>
#> 1 2010000002
#> 2 2010000004
#> 3 2010000005
#> 4 2010000006
#> 5 2010000091
#> 6 2010000147
#> 7 2010000281
#> 8 2010000287
#> 9 2010000346
#> 10 2010000353
#> # ℹ 19,375 more rows
testmat %>%
write_rds("待计算.rds")

不过这里我们就不完全计算了,而是选择 20 个:

# 此处代码需要下载讲义材料查看~
#> Time difference of 2.751031 mins

通过这个结果我们就可以估算计算全部的需要多长时间了。

显然单线程计算这个是个巨耗时的工作,改用多线程:

# 此处代码需要下载讲义材料查看~

这部分代码我单独存放的,可以方便直接全选运行。电脑性能不好的小伙伴还是建议老老实实单线程。

合并计算结果:

library(tidyverse)
# 合并计算结果
fs::dir_ls("res1") %>%
lapply(readr::read_csv, col_names = F) %>%
bind_rows() %>%
set_names("newipzlid", "cd_index") -> cdindex

# 没有后向引用的
read_rds("nobackrefs.rds") %>%
set_names("newipzlid", "cd_index") -> nobackrefs

然后我们再次读取和处理上市公司专利,这次我们就需要进行去重了,不过我们需要尽可能保留出现在 patent_citations 里面的专利:

# unique(c(patent_citations$newipzlid, patent_citations$newipzlid2)) %>%
# as_tibble() %>%
# set_names("newipzlid") -> unique_newipzlid
#
# unique_newipzlid
#
# unique_newipzlid %>%
# write_rds("具有引用与被引用信息的专利unique_newipzlid.rds")

附件中也提供了这个文件:“具有引用与被引用信息的专利unique_newipzlid.rds”。

read_rds("/Volumes/ADT/常用大数据/具有引用与被引用信息的专利unique_newipzlid.rds") -> unique_newipzlid

# 上市公司专利数据
readr::read_csv("2010年上市公司与专利数据匹配结果.csv") -> df1
df1 %>%
left_join(unique_newipzlid %>% mutate(value = 1)) -> df1b

# 由于专利数据里面包含重复的,所以我们要进行去重,不过去重的时候要尽可能保留 unique_newipzlid 里面的
df1b %>%
mutate(公开公告号 = str_remove(公开公告号, "[A-Z]$")) %>%
mutate(value = if_else(is.na(value), 0, value)) %>%
# 根据公开公告号去重
group_by(股票代码, 年份, 公开公告号) %>%
filter(value == max(value, na.rm = T)) %>%
ungroup() %>%
distinct(股票代码, 年份, 公开公告号, .keep_all = T) %>%
select(-公开公告号) %>%
# 再根据申请号去重
group_by(股票代码, 年份, 申请号) %>%
filter(value == max(value, na.rm = T)) %>%
ungroup() %>%
distinct(股票代码, 年份, 申请号, .keep_all = T) %>%
select(-申请号, -value) %>%
select(股票代码, newipzlid, 年份) -> patent_info

patent_info

和上述计算结果链接:

# 和上述计算结果链接
patent_info %>%
left_join(
bind_rows(cdindex, nobackrefs)
) -> dfres

dfres %>%
mutate(cd_index = if_else(is.na(cd_index), 0, cd_index)) -> dfres

dfres

由于上面并没有计算所有的专利 CD 指数,这里这里直接用 0 代替缺失的其实是不对的,但是如果你计算了全部可计算的专利,这样做是可以的。没有被引用的专利 CD 指数是 0。

分公司汇总就可以得到公司层面的结果了:

dfres %>%
group_by(股票代码, 年份) %>%
summarise(cd_index = mean(cd_index, na.rm = T), .groups = "drop")

仔细学习的小伙伴也会发现,其实这个代码是可以计算全部专利 CD 指数的,不过由于运算过于耗时,还是只计算自己需要的就好。

点击这里跳转到 RStata 短书平台获取附件:名师讲堂|使用 R 语言测算专利的创新突破度:CD指数(更新)

评论