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后,经过统一清洗层再流向建模与报告的整体数据流结构。
三、案例场景:季度赔付数据的端到端流转
假设你负责某车险公司的季度赔付分析,数据分散在三个位置:理赔明细在CSV中,承保信息在Excel中,历史赔付率基准通过API获取。一个稳健的流程如下:
- 用
read_csv()读取理赔明细,显式声明GBK编码与业务缺失标记。 - 用
read_excel()读取承保表,跳过表头说明行,统一保单号为主键。 - 用
httr2拉取行业基准,解析JSON后转为数据框。 - 以
policy_id为键合并三份数据,校验行数与重复键。 - 建模后把结果写回数据库,并生成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")
config.yml中,用config包读取,避免硬编码;报告模板中只保留展示逻辑,数据处理全部前置到独立脚本。
五、误区与复盘
- 误区一:默认参数走天下。不同来源的CSV编码与缺失约定不同,必须逐源确认。
- 误区二:把整表读入内存再过滤。数据库场景应把过滤条件下推到SQL,减少传输与内存压力。
- 误区三:忽略连接与凭据管理。数据库连接要
dbDisconnect(),用环境变量而非写进脚本。 - 误区四:报告与数据耦合。报告模板中直接读原始数据,会导致重跑时结果不可复现。
把数据进出当作建模流程中与模型同等重要的一环,你的精算分析才具备可复现、可审计、可交付的工程品质。