测定站的记录几乎不可能每天都有:猪某天没进站、耳标没读到、设备故障,或者进站晚、出栏早。tidypptsda 不绕开缺失,而是先把计算区间(默认 30–120 kg)内的每一天都补上体重和采食量,再计算日增重(ADG)、日采食量(ADFI)和料肉比(FCR)。补的方法取决于缺失的位置:在两个有记录的日子之间,还是在首个记录之前、末个记录之后。

本文用一头示例猪逐步演示每一种填充,说明它由哪个参数控制、结果里用什么标签标出。

1. 逐步演示

下面这头猪来自示例数据 nedap。为了让每种情况都出现,人为删掉了它的一部分访问:区间开头 12 天(晚进站)、中间 2 天(短缺口)、中间 6 天(长缺口)和结尾 8 天(早出栏)。图中所有的点和线都是包实际算出来的结果。点 ▶ 播放 自动演示,也可以用 上一步 / 下一步 逐步查看。

03060901200100020003000400004月05月体重 (kg)日采食 (g)120 kg30 kg区间起点区间终点ADG = 1208 g/d(回归斜率)ADFI = 2750 g/d FCR = 2.28
实测 / 无错误校正中间填充首尾填充空心:不用的记录 / 群体曲线

2. 位置、方法与参数一览

缺失位置 体重:方法 体重:参数 source 采食:方法 采食:参数 state
有记录的日子 当天有效记录的中位数 weight_range、adapt_days、weight_track、weight_outlier observed 记录值;有错误访问时混合模型校正 dfi_correct、dfi_range、dfi_quantile org / corr
两个有记录的日子之间,≤ 3 天 线性插值 — interpolated 稳健 LOESS long_gap_days、loess_span iii
两个有记录的日子之间,> 3 天 线性插值 — interpolated 群体曲线 × 个体比例 impute_method = "loess_pop" iii
首个记录之前(晚进站) 切线外推 curve_extrap = "start"、max_extrap_days curve 群体曲线 × 个体比例 impute_method = "loess_pop" elrp
末个记录之后(早出栏) 生长曲线 curve_model、max_extrap_days curve 这头猪自己的”日采食 ~ 体重”回归 impute_method elrp
外推后仍到不了 30 或 120 kg 不填,只算覆盖到的部分 max_extrap_days — 只在覆盖到的部分计算 — —

几点说明:

  • “有记录”对体重和采食分别判断。进站后 7 天的适应期内,体重不用,但采食照常计入;所以同一天可能体重是填充的,采食是实测的。
  • 体重的”首尾”以有效体重记录为界,采食的”首尾”以有采食记录的日子为界,两者不一定是同一天。
  • 采食插补用的体重,是上一步补齐后的日体重(adg$daily$weight_filled)。所以体重填充的方法也会影响采食的填充。
  • 所有填充只在计算区间内进行,区间之外的日子不补。

3. 每种方法的细节

体重:线性插值

前后两个有记录日子的日体重之间连成直线。中间缺失时,前后两天的实测最能说明这头猪当时的状态;在示例数据上,人为隐藏中间的日子再填回,线性插值的日体重误差约 0.8 kg,用生长曲线约 1.1–1.6 kg(见《方法验证》)。

体重:首尾外推

curve_extrap 首个记录之前 末个记录之后
"start"(默认) 切线:首 14 天记录的稳健斜率 生长曲线
"tangent" 切线 切线:末 14 天记录的稳健斜率
"curve" 生长曲线 生长曲线

切线的斜率限制在 0.2–1.5 kg/d。二次、三次曲线往记录外推几周时容易弯折;在 457 头 NEDAP 猪的无泄露检验中,开头缺 20 天时,开头用切线使 ADG 误差从 81 降到 66 g/d、FCR 排名相关从 0.82 升到 0.86。末端用切线虽然 ADG 更准,但采食是按体重外推的,FCR 排名反而变差,所以默认只在开头用切线。

采食:群体曲线 × 个体比例

日采食 = r × f(体重)
  • f:全部猪区间内有记录日子的二次回归 日采食 ~ 体重 + 体重²;有 breed 列时,不少于 10 头的品种各拟合一条,其余的猪用全群曲线;超出拟合时的体重范围,取端点的值。曲线系数在 adfi$pop_curve 中。
  • r:这头猪有记录日子的采食之和 ÷ 同样日子上 f 之和,再按记录天数 n 向 1 收缩(权重 n / (n + 10))。
  • 曲线提供采食随体重变化的形状,r 保留这头猪自己的水平。

采食:末端用单猪回归

末个记录之后,用这头猪全部有记录日子的 日采食 ~ 体重 线性回归外推(结果不小于 0)。早出栏的猪,末期采食的上升趋势已经在它自己的数据里;群体二次曲线在高体重段变平,在无泄露检验中平均每天低估 43–129 g,会让早出栏的猪显得更省料。

采食:短缺口 LOESS

两个有记录日子之间不超过 long_gap_days 天的缺口,用日采食对日期的稳健 LOESS 平滑(loess_span,默认 0.75)插补。缺口短时,前后几天的采食最能代表当时的水平。

4. 改用其他方法

# 两端都用生长曲线外推(1.4.0 之前的做法)
qc_config(curve_extrap = "curve")
# 长缺口和两端都用这头猪自己的回归(1.3.0 之前的做法)
qc_config(impute_method = "loess_lm")
# 晚进站多、记录短的批次:直线生长曲线
qc_config(curve_model = "poly1", stage_range = c(60, 120))

换方法之前,先用 validate_truncation() 在自己的数据上比较:它删掉一部分猪若干天的全部访问后重跑整个流程,与全部数据的结果比较,不会有被隐藏的信息泄露回来(见《方法验证》)。

res <- validate_truncation(
  visits,
  settings = list(default = qc_config(),
                  curve   = qc_config(curve_extrap = "curve"),
                  lm      = qc_config(impute_method = "loess_lm")),
  scenarios = c("late20", "early20", "gap10")
)
res$summary[trait == "fcr"]

5. 填了多少,怎么查

a$per_pig[responder == pig, .(adg, adg_type, n_observed_days, n_interpolated_days,
                              n_curve_days, extrap_days_start, extrap_days_end)]
#>         adg     adg_type n_observed_days n_interpolated_days n_curve_days
#>       <num>       <char>           <int>               <int>        <int>
#> 1: 1207.561 extrapolated              41                   8           26
#>    extrap_days_start extrap_days_end
#>                <num>           <num>
#> 1:          14.75088        10.97878
f$per_pig[responder == pig, .(adfi, coverage, n_observed, n_interpolated, n_extrapolated,
                              n_extrap_start, n_extrap_end)]
#>        adfi coverage n_observed n_interpolated n_extrapolated n_extrap_start
#>       <num>    <num>      <int>          <int>          <int>          <int>
#> 1: 2750.359     0.64         48              8             19              8
#>    n_extrap_end
#>           <int>
#> 1:           11
  • a$daily$source 和 f$daily$state 标出每一天的来源;plot_adg() 和 plot_adfi() 用不同的颜色画出来。
  • 填充的天数越多,结果越依赖模型。calc_fcr() 会把两端连续填充超过 max_edge_extrap_days(15)天、或采食实测比例低于 min_coverage(0.7)的猪标为 low_confidence。