我正在处理人口普查(CTPP)数据,并且GEOID字段是一个长字符串,其中包含许多地理信息。对于各种人口普查表,此字符串的格式都会更改,但是它们提供了代码查找。这是一个示例GEOID和格式“代码”。 (我已经可以解析的部分已被删除。这是我无法解析的GEOID的一部分。)
geoid <- "0202000000126"
format <- "ssccczzzzzzzz"
这意味着前两个字符("02"
)表示州(阿拉斯加),后三个字符("020"
)表示县,其余字符表示区域。
我有一张这些大地水准面/格式对的表格,每行的格式可以不同。
df <- data.frame(
geoid = c(
"0224230",
"0202000000126"
),
format = c(
"ssppppp",
"ssccczzzzzzzz"
)
)
# A tibble: 2 x 2
geoid format
<chr> <chr>
1 0224230 ssppppp
2 0202000000126 ssccczzzzzzzz
我想做的是将geoid
列分成每个地理位置的列,如下所示:
# A tibble: 2 x 6
geoid format s p c z
<chr> <chr> <chr> <chr> <chr> <chr>
1 0224230 ssppppp 02 24230 NA NA
2 0202000000126 ssccczzzzzzzz 02 NA 020 00000126
我研究了几种方法。 extract()
中的stringr
看起来很有希望。我也很确定我需要一个自定义函数,该函数将mapply(?)/ map映射到我的数据框上。
答案 0 :(得分:2)
一个base
替代项:
geo_codes <- c("s", "c", "p", "z")
# get starting position and lengths of consecutive characters in 'format'
g <- gregexpr("(.)\\1+", df$format)
# use the result above to extract corresponding substrings from 'geoid'
geo <- regmatches(df$geoid, g)
# select first element in each run of 'format' and split
# used to name substrings from above
fmt <- strsplit(gsub("(.)\\1+", "\\1", df$format), "")
# for each element in 'geo' and 'fmt',
# 1. create a named vector
# 2. index the vector with 'geo_codes'
# 3. set names of the full length vector
t(mapply(function(geo, fmt){
setNames(setNames(geo, fmt)[geo_codes], geo_codes)},
geo, fmt))
# s c p z
# [1,] "02" NA "24230" NA
# [2,] "02" "020" NA "00000126"
另一种选择
geo <- strsplit(df$geoid, "")
fmt <- strsplit(df$format, "")
t(mapply(function(geo, fmt) unlist(lapply(split(geo, factor(fmt, levels = geo_codes)), function(x){
if(length(x)) paste(x, collapse = "") else NA})), geo, fmt))
以2e5行为基准,我的第一种方法比第二种方法快约2倍。
答案 1 :(得分:0)
通常情况下,写下问题和最小示例可以帮助我简化问题并找到解决方案。我敢肯定有一个更好的解决方案,但这就是我想出的,很容易使您明白。
尽管格式不同,但唯一字符的数量有限。在此问题的玩具示例中,仅s, c, p, z
。所以这就是我所做的:
首先,我创建了一个函数,该函数采用单个格式字符串,单个大地水准面字符串和单个subgeo字符/代码。该函数确定format
中的哪些字符位置与subgeo
相匹配,然后从geoid
返回这些位置。
extract_sub_geo <- function(format, geoid, subgeo) {
geoid_v <- unlist(strsplit(geoid, ""))
format_v <- unlist(strsplit(format, ""))
positions <- which(format_v == subgeo)
result <- paste(geoid_v[positions], collapse = "")
return(result)
}
extract_sub_geo("ssccczzzzzzzz", "0202000000126", "s")
[1] "02"
然后,我遍历每个唯一的代码,并使用pmap()
将函数应用于我的整个数据帧。
geo_codes <- c("s", "c", "p", "z")
for (code in geo_codes) {
df <- df %>%
mutate(
!!code := pmap_chr(list(format, remainder, !!(code)), extract_sub_geo)
)
}
# A tibble: 2 x 6
geoid format s c p z
<chr> <chr> <chr> <chr> <chr> <chr>
1 0224230 ssppppp 02 "" 02000 ""
2 0202000000126 ssccczzzzzzzz 02 020 "" 00000126
在基数R中而不是dplyr中执行循环可能更清洁。
答案 2 :(得分:0)
tidyverse 解决方案:
library(tidyverse)
create_new_code <- function(id, format, char) {
format %>%
str_locate_all(paste0(char, "*", char)) %>%
unlist() %>%
{substr(id, .[1], .[2])}
}
create_new_codes <- function(id, format) {
c("s", "p", "c", "z") %>%
set_names() %>%
map(create_new_code, id = id, format = format)
}
bind_cols(df,
with(df, map2_df(geoid, format, create_new_codes)))
# geoid format s p c z
#1 0224230 ssppppp 02 24230 <NA> <NA>
#2 0202000000126 ssccczzzzzzzz 02 <NA> 020 00000126