4. 数据导入与导出

精算建模的第一步不是拟合分布,而是把数据可靠地"请进来";最后一步也不是算出结果,而是把结论稳定地"送出去"。本章围绕R语言中数据进出的完整链路展开,覆盖CSV、Excel、数据库与API四类主流数据源,并延伸到结果输出与报告生成,帮助你把零散的数据处理动作组织成可复用的工程流程。

一、核心原理:数据进出的三条边界

无论数据来自哪种介质,R与外部世界交互时始终要跨越三条边界:编码边界(字符集、日期格式、缺失值标记)、类型边界(文本到数值、因子到字符、时区到本地时间)和结构边界(宽表与长表、嵌套JSON与扁平数据框)。理解这三条边界,就能预判绝大多数导入导出问题的根源。

以CSV为例,read.csv()默认把空字符串视为缺失,把"NA"也视为缺失,但保险数据中常出现"-"、"未知"、"9999"等业务缺失标记,若不显式声明,它们会被当作有效值参与建模,直接污染赔付频率与赔付强度的估计。因此,导入参数的本质是"告诉R如何翻译外部世界的约定"。

核心原则:导入时显式声明编码、缺失值与列类型;导出时显式声明分隔符、编码与小数精度。默认值只适合演示,不适合生产。

二、方法流程:四类数据源的读取与写出

1. CSV与文本文件。基础包read.csv()足够应付简单场景,但生产环境推荐readr或data.table,速度更快且类型推断更稳定。

library(readr)
claims <- read_csv(
  "claims_2023.csv",
  locale = locale(encoding = "GBK"),
  na = c("", "NA", "-", "未知"),
  col_types = cols(
    policy_id = col_character(),
    accident_date = col_date(format = "%Y/%m/%d"),
    claim_amount = col_double()
  )
)
write_excel_csv(claims, "claims_clean.csv")

2. Excel工作簿。Excel的坑在于多工作表、合并单元格与日期序列号。readxl无需Java依赖,适合读取;openxlsx适合写出带格式的报表。

library(readxl)
sheets <- excel_sheets("underwriting.xlsx")
uw <- read_excel("underwriting.xlsx", sheet = "2023Q4", skip = 2)

3. 数据库。精算数据常驻留在Oracle、SQL Server、PostgreSQL或MySQL中。推荐DBI配合odbc或RPostgres,用SQL下推过滤,避免把整表拉进内存。

library(DBI)
con <- dbConnect(odbc::odbc(), dsn = "actuary_dw", uid = "analyst")
loss <- dbGetQuery(con, "
  SELECT policy_id, accident_date, paid_loss
  FROM fact_claims
  WHERE accident_date >= '2023-01-01'
")
dbDisconnect(con)

4. API数据。费率表、汇率、行业基准等常通过REST接口提供。用httr2发起请求,用jsonlite解析嵌套结构。

library(httr2); library(jsonlite)
resp <- request("https://api.example.com/v1/rate") |>
  req_headers(Authorization = paste("Bearer", Sys.getenv("API_"))) |>
  req_perform()
rate <- resp_body_json(resp, simplifyVector = TRUE)

下图展示了四类数据源进入R后,经过统一清洗层再流向建模与报告的整体数据流结构。

保险数据进出R的完整链路 CSV / 文本 readr / data.table Excel 工作簿 readxl / openxlsx 数据库 DBI / odbc API 接口 httr2 / jsonlite 统一清洗层 编码 / 缺失值 类型 / 日期 / 时区 宽长表转换 业务规则校验 建模与统计 GLM / 频率强度 结果输出 CSV / Excel / DB 报告生成 R Markdown / Quarto

三、案例场景:季度赔付数据的端到端流转

假设你负责某车险公司的季度赔付分析,数据分散在三个位置:理赔明细在CSV中,承保信息在Excel中,历史赔付率基准通过API获取。一个稳健的流程如下:

  1. 用read_csv()读取理赔明细,显式声明GBK编码与业务缺失标记。
  2. 用read_excel()读取承保表,跳过表头说明行,统一保单号为主键。
  3. 用httr2拉取行业基准,解析JSON后转为数据框。
  4. 以policy_id为键合并三份数据,校验行数与重复键。
  5. 建模后把结果写回数据库,并生成Quarto报告。
数据源 推荐包 关键参数 常见风险
CSV readr locale、na、col_types 编码错乱、缺失标记误判
Excel readxl sheet、skip、range 合并单元格、日期序列号
数据库 DBI + odbc dsn、SQL下推过滤 连接泄漏、全表拉取
API httr2 + jsonlite 、超时、重试 限流、嵌套结构解析失败

四、结果输出与报告生成

输出环节同样需要工程化。结构化结果用write_csv()或dbWriteTable()落地;面向业务方的报告用R Markdown或Quarto,把参数化模板与数据绑定,实现"一次编写、按季度重跑"。

library(rmarkdown)
render("quarterly_report.Rmd",
       params = list(quarter = "2024Q1"),
       output_file = "report_2024Q1.html")
实用建议:把导入参数、SQL语句与API地址集中放在config.yml中,用config包读取,避免硬编码;报告模板中只保留展示逻辑,数据处理全部前置到独立脚本。

五、误区与复盘

  • 误区一:默认参数走天下。不同来源的CSV编码与缺失约定不同,必须逐源确认。
  • 误区二:把整表读入内存再过滤。数据库场景应把过滤条件下推到SQL,减少传输与内存压力。
  • 误区三:忽略连接与凭据管理。数据库连接要dbDisconnect(),用环境变量而非写进脚本。
  • 误区四:报告与数据耦合。报告模板中直接读原始数据,会导致重跑时结果不可复现。
复盘要点:每次数据导入后,立即检查行数、列类型、缺失比例与主键唯一性。这四项检查能在建模前拦截绝大多数数据质量问题,其价值远高于事后调参。

把数据进出当作建模流程中与模型同等重要的一环,你的精算分析才具备可复现、可审计、可交付的工程品质。