R如何使用格式代码/字符串将字符串列拆分为多列?

时间:2019-04-19 19:50:56

标签: r string dplyr tidyr stringr

我正在处理人口普查(CTPP)数据,并且GEOID字段是一个长字符串,其中包含许多地理信息。对于各种人口普查表,此字符串的格式都会更改,但是它们提供了代码查找。这是一个示例GEOID和格式“代码”。 (我已经可以解析的部分已被删除。这是我无法解析的GEOID的一部分。)

geoid <- "0202000000126"
format <- "ssccczzzzzzzz"

这意味着前两个字符("02")表示州(阿拉斯加),后三个字符("020")表示县,其余字符表示区域。

我有一张这些大地水准面/格式对的表格,每行的格式可以不同。

  • s:状态
  • c:县
  • p:地点
  • z:区域
  • (在此简单示例中未使用的其他对象)
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映射到我的数据框上。

3 个答案:

答案 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