使用 R 语言统一海关税号编码

一、为什么要统一海关税号编码

海关数据中的商品编码(税号编码),也就是我们常说的 HS 编码(Harmonized System,商品名称及编码协调制度),是国际贸易中对商品进行分类的通用语言。它有两个特点需要特别注意:

  1. 前 6 位是国际通用码,全球统一;后 2 位是国内子目,由各国海关自行扩展。我国海关数据中的商品编码通常为 8 位。
  2. HS 编码每隔约 5 年修订一次。世界海关组织(WCO)会新增、删除、拆分或合并部分税目,因此不同年份的数据使用的是不同版本的 HS 编码。

在我们的海关数据(2000–2016 年)中,年份与 HS 版本的对应关系如下:

library(tidyverse)

year_hs <- tibble(
year = 2000:2016,
hs_version = c(rep("HS1996", 2), rep("HS2002", 5),
rep("HS2007", 5), rep("HS2012", 5))
)
year_hs %>%
group_by(hs_version) %>%
summarise(年份区间 = paste0(min(year), "–", max(year)), .groups = "drop") %>%
rename(HS版本 = hs_version) %>%
knitr::kable(align = "c")

如果直接把跨年份的数据放在一起做面板分析,同一个商品在不同年份可能对应不同的编码,会导致匹配错误。因此在做任何跨期分析之前,必须先把所有年份的编码统一到同一个版本。

本讲义演示如何用 R 把海关数据统一到 HS1996 / HS2002 / HS2007 / HS2012 / HS2017 五个版本,并保留原始编码。为节省篇幅,正文以 2014–2016 年(均为 HS2012 版本)的数据为例;处理全部年份只需把年份向量改为 2000:2016 即可。

二、数据准备

原始海关数据体量庞大,我们先用 Stata 把每年的数据抽取出 newhgid(观测 ID)、年份、商品编码 三个变量,另存为分年 .dta 文件,减小后续 R 读取的压力:

cd "/Users/ac/Desktop/使用 R 语言统一海关税号编码/"
cap mkdir "海关数据商品编码分年"
forval y = 2000/2016 {
use ".../海关数据分年/`y'.dta", clear
keep newhgid 年份 商品编码
save "海关数据商品编码分年/`y'", replace
}

三、读取 HS 转换表

版本之间的转换依赖联合国统计司提供的官方对照表。每个 Excel 文件里通常有两张表:

  • Conversion Table:只保留在新版本中仍有对应关系的旧码,覆盖不全;
  • Correlation Table:记录 1:1 / 1:n / n:1 / n:n 全部对应关系,覆盖度更高。

转换表可以从这里下载:https://unstats.un.org/unsd/classifications/Econ

因此我们统一使用 Correlation Table。所有转换表方向都是「新版本 → 旧版本」,其中第 1 列是新版本码、第 3 列是旧版本码。我们分别按正向(旧→新)和反向(新→旧)读取,一对多时取第一个匹配:

library(tidyverse)
library(haven)
library(readxl)

# 路径设置
base_dir <- "/Users/ac/Desktop/使用 R 语言统一海关税号编码"
data_dir <- file.path(base_dir, "海关数据商品编码分年")
conv_dir <- file.path(base_dir, "HS代码转换对照表")

# 正向(旧→新):以旧码去重
# 此处代码需下载讲义材料查看~

# 反向(新→旧)
bwd_02to96 <- read_corr_backward(file_02to96, "Correlation table")
bwd_07to02 <- read_corr_backward(file_07to02, "Correlation Tables")
bwd_12to07 <- read_corr_backward(file_12to07, "Correlation HS2012-HS2007")
bwd_17to12 <- read_corr_backward(file_17to12, "Correlation HS17-HS12")

# 看一眼正向表长什么样
head(fwd_12to17)
#> # A tibble: 6 × 2
#> from_old to_new
#> <chr> <chr>
#> 1 010121 010121
#> 2 010129 010129
#> 3 010130 010130
#> 4 010190 010190
#> 5 010221 010221
#> 6 010229 010229

四、构建完整查找表

有了相邻版本的对照关系后,就可以把它们链式拼接起来,为每个源版本构建一张包含全部 5 个目标版本的查找表。核心技巧是级联填充:若某一步转换缺失(NA),则沿用上一版本的码,保证链条不中断。

以 HS2012 源(对应 2014–2016 年数据)为例——它向前只需一步到 HS2017,向后需要依次退回到 HS2007、HS2002、HS1996:

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

其余三个源版本(HS1996 / HS2002 / HS2007)的查找表构建方式完全类似,只是正向、反向链条的长度不同,完整代码见配套脚本 使用 R 语言统一海关税号编码.R。

五、执行编码转换

商品编码为 8 位:前 6 位参与版本转换,后 2 位(国内子目)保留不变。转换时用命名向量做「查表 → 缺失填原码 → 拼回后 2 位」,对上千万行数据也非常高效:

# 用 2014 年数据演示(源版本 HS2012)
# 此处代码需下载讲义材料查看~

# 展示几个「向前/向后都发生了变化」的例子
result %>%
distinct() %>%
filter(HS2012 != HS1996 | HS2012 != HS2017) %>%
head(10)

可以看到,源版本变量(这里是 HS2012)与原始 商品编码 完全一致(恒等转换),而其他版本会随着税目的历史变动而不同。最终把 HS1996 ~ HS2017 五个新变量与原始变量一起写回 .dta,即完成统一。

六、统一质量:无法统一的比例

并非所有编码都能成功统一。若某个 6 位码不是目标版本中的合法 HS 编码(例如中国海关特有的 98/99 章特殊码、或数据录入错误),它就无法被统一,我们保留其原码。下面按观测数加权,统计 2014–2016 年各版本无法统一的比例:

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

再用 ggplot2 画成热力图,直观展示各年各版本的无法统一比例:

ver_levels <- c("HS1996", "HS2002", "HS2007", "HS2012", "HS2017")

plot_df <- miss_stats %>%
mutate(target_version = factor(target_version, levels = ver_levels),
year = factor(year),
lbl = if_else(unmatched_pct == 0, "0", sprintf("%.3f", unmatched_pct)))

ggplot(plot_df, aes(x = target_version, y = fct_rev(year), fill = unmatched_pct)) +
geom_tile(color = "white", linewidth = 0.6) +
geom_text(aes(label = lbl, color = unmatched_pct > 0.7),
size = 4.5, family = cnfont, show.legend = FALSE) +
scale_color_manual(values = c("TRUE" = "white", "FALSE" = "grey15")) +
scale_fill_gradientn(
colors = c("#f7fbff", "#c6dbef", "#6baed6", "#2171b5", "#d94801", "#a50f15"),
values = scales::rescale(c(0, 0.01, 0.02, 0.05, 0.5, 1.334)),
name = "无法统一\n比例 (%)") +
labs(title = "2014–2016 各版本 HS 编码无法统一的观测值比例",
x = "统一目标版本", y = "年份(源版本 HS2012)",
caption = "数据:中国海关商品编码 | HS 代码转换表来源:https://unstats.un.org/unsd/classifications/Econ") +
scale_x_discrete(position = "top") +
theme_minimal(base_family = cnfont, base_size = 13) +
theme(plot.title = element_text(face = "bold"),
panel.grid = element_blank(),
legend.key.height = unit(1.1, "cm"))

从图中可以看出:

  • 2014 年几乎完美,无法统一比例为 0%;
  • 2015 年约 0.04%,主要是少量海关特殊码;
  • 2016 年明显偏高(约 1.33%),原因是当年 980400 系列跨境电商监管码大量出现——它一个码就占了约 23 万条观测,而这类码根本不属于国际 HS 体系,因此在任何版本下都无法统一。

七、小结

统一海关税号编码的完整流程可以概括为四步:

  1. 抽取变量:用 Stata 把大数据瘦身为 newhgid + 年份 + 商品编码;
  2. 读取 Correlation Table:正、反两个方向分别构建相邻版本的对照关系;
  3. 链式拼接 + 级联填充:为每个源版本构建到 5 个目标版本的查找表;
  4. 向量化转换:前 6 位查表转换、后 2 位保留,缺失则保留原码。

除个别中国海关特殊码与数据录入错误外,绝大多数年份的统一成功率都在 99.9% 以上,可以放心用于跨期面板分析。完整可运行代码见配套脚本 `使用 R 语言统一海关税号编码。

点击这里跳转到 RStata 短书平台获取附件:使用 R 语言统一海关税号编码

评论