基于日期排序从长表提取市镇编码初始与最终值(tidyverse实现)
问题描述
我正在处理法国INSEE的v_mvt_commune_2024.csv文件,该文件记录了1943年以来法国市镇的行政变更事件。我关注DATE_EFF(生效日期)、COM_AV(变更前市镇编码)和COM_AP(变更后市镇编码)变量,已通过以下代码筛选出编码发生变更的行:
data <- readr::read_csv("v_mvt_commune_2024.csv") |> dplyr::select(DATE_EFF, COM_AV, COM_AP) |> dplyr::filter(COM_AV != COM_AP)
部分数据显示,部分市镇编码会经历多次变更(例如64031先变为64113,后又变回64031)。我需要生成一张映射表,其中COM_AV为初始编码,COM_AP为该编码经过所有变更后的最终值。请问如何用tidyverse高效实现这一需求?
高效实现方案
处理这类链式编码变更,最高效的思路是将变更关系建模为有向图:每个市镇编码是图的节点,每条变更记录是从COM_AV指向COM_AP的有向边,最终找到每个节点对应的终端节点(无后续变更的编码)。结合tidyverse生态,推荐使用tidygraph包处理图结构,具体步骤如下:
1. 加载依赖包
library(tidyverse) library(tidygraph)
2. 预处理数据并构建有向图
首先按生效日期排序,确保变更顺序符合时间逻辑(最新变更优先),再将数据转为有向图结构:
# 按生效日期排序,保证变更时序正确 sorted_changes <- data |> arrange(DATE_EFF) # 将变更记录转为有向图:COM_AV → COM_AP 为一条边 commune_graph <- sorted_changes |> as_tbl_graph(directed = TRUE)
3. 计算初始编码到最终编码的映射
通过图分析找到每个节点的终端节点(无出边的节点,即不再发生变更的编码):
final_mapping <- commune_graph |> activate(nodes) |> mutate( final_code = map_chr(node_is_sink(), function(is_sink) { # 终端节点的最终编码就是自身 if (is_sink) { name } else { # 广度优先搜索找到该节点最终指向的终端节点 node |> bfs(mode = "out", unreachable = FALSE) |> filter(node_is_sink()) |> pull(name) |> first() } }) ) |> as_tibble() |> rename(COM_AV = name, COM_AP = final_code)
4. 补充未变更的编码(可选)
如果需要包含从未发生变更的市镇编码,可以从原始数据中提取所有唯一编码,补充到映射表中:
# 提取所有出现过的市镇编码 all_codes <- data |> pivot_longer(cols = c(COM_AV, COM_AP), values_to = "code") |> distinct(code) # 合并映射表,未变更的编码最终值为自身 full_mapping <- all_codes |> left_join(final_mapping, by = c("code" = "COM_AV")) |> mutate(COM_AP = coalesce(COM_AP, code)) |> rename(COM_AV = code)
方案说明
- 按
DATE_EFF排序确保变更时序正确,避免旧变更覆盖新变更的逻辑错误。 - 图结构能高效处理链式、循环变更(如64031→64113→64031的情况,最终终端节点为64031)。
- 广度优先搜索(BFS)保证在大规模数据下的查询效率,比递归遍历更稳定。
内容的提问来源于stack exchange,提问作者pure_func
相关产品推荐
相关产品推荐

