R中的条带变量

Banding Variable in R

对于以下代码:

x <- data.frame(year = c(1730, 1860, 1941, 2011))

century_bands <- data.frame(min_year = c(1700, 1800, 1900, 2000),
                            max_year = c(1799, 1899, 1999, 2099),
                            century_name = c("18th", "19th", "20th", "21st"))

对于 x 中的每个值,我想使用 century_bands 中的信息计算出它所属的世纪的名称。我无法想象这很难实现,但我无法弄清楚。有人可以帮忙吗?有没有办法使用 dplyr 包(我用得很多)或者其他一些技术?

这只是现实生活中的一个非常简单的例子,在这种情况下,乐队并没有整齐地排列 100 年的步长 - 因此,任何基于将年份除以 100 等的快捷方式都不会奏效。

谢谢。

利用 fuzzyjoin 的一个选项可能是:

fuzzy_left_join(x, century_bands, 
                by = c("year" = "min_year",
                       "year" = "max_year"),
                match_fun = list(`>=`, `<=`)) 

  year min_year max_year century_name
1 1730     1700     1799         18th
2 1860     1800     1899         19th
3 1941     1900     1999         20th
4 2011     2000     2099         21st

由于 max_year 列似乎是多余的,您也可以轻松地做到:

century_bands[colSums(sapply(x$year, function(x) `>=`(x, century_bands$min_year))), 3]
# [1] "18th" "19th" "20th" "21st"

这里有一些方法。

1) sqldf 在 SQL 中,可以在复杂条件下进行连接。如果 year 大于或等于下限且小于或等于上限,则使用 between 的语法匹配。对于特定年份,如果没有匹配项,左连接将导致使用 NA(尽管问题示例中不会出现这种情况)。

library(sqldf)
sqldf("select year, century_name from x
  left join century_bands on year between min_year and max_year")
##   year century_name
## 1 1730         18th
## 2 1860         19th
## 3 1941         20th
## 4 2011         21st

2) findInterval 此方法仅使用基数 R。对于其第一个参数的每个组件 findInterval returns 第二个参数中值的数量小于或等于它。假定第二个参数按升序排序。 findInterval 返回的数字可用于索引 century_namefindInterval 往往效率很高。

transform(x, year_name = 
  with(century_bands, century_name[findInterval(year, min_year)]))
##   year year_name
## 1 1730      18th
## 2 1860      19th
## 3 1941      20th
## 4 2011      21st

尽管问题中不会出现这种情况,但如果 year 可能在所有频段之外,那么我们可以在不更改代码的情况下通过向与 century_bands 关联的 century_bands 添加额外的行来扩展它=26=] 的 NA 否则我们可以扩展 findInterval liek this:

FindInterval <- function(x, vec, upper) {
  ifelse(x < vec[1] | x > upper, NA, findInterval(x, vec))
}
transform(x, year_name = 
  with(century_bands, century_name[FindInterval(year, min_year, max(max_year))]))

如果仍然使用 dplyr,我们可以将 transform 替换为 mutate;否则,使用 transform 消除了这种依赖性。

3) sapply 另一种基础解决方案是

Match <- function(x) with(century_bands, century_name[x >= min_year & x <= max_year])
transform(x, century_name = sapply(year, Match))
##   year century_name
## 1 1730         18th
## 2 1860         19th
## 3 1941         20th
## 4 2011         21st

如果所有年份都在范围内,这应该足够了。问题示例中就是这种情况,但如果不能保证这一点,则像这样扩展 Match

Match <- function(x) {
  Name <- with(century_bands, century_name[x >= min_year & x <= max_year])
  if (length(Name)) Name else NA
}

4) cut 这个基本解决方案类似于 findInterval 但它 returns NA 如果 year 不在任何波段内.

transform(x, year_name = with(century_bands, century_name[
    cut(year, c(min_year, max(max_year)), label = FALSE, include.lowest = TRUE)
]))

5) car::recode 此函数允许按如下方式重新编码值。

library(car)

recodes <- 
  "1700:1799='18th'; 1800:1899='19th'; 1900:1999='20th'; 2000:2099='21st'; else=NA"
transform(x, year_name = recode(year, recodes))
##   year year_name
## 1 1730      18th
## 2 1860      19th
## 3 1941      20th
## 4 2011      21st

为了避免对 recodes 字符串进行硬编码,它可以像这样从 century_bands 派生出来

recodes <- with(century_bands, 
  paste(sprintf("%d:%d='%s'", min_year, max_year, century_name), collapse = ";")
)
recodes <- paste0(recodes, "; else=NA")

6) expand bands 我们可以将 bands 扩展到单独的年份,在这种情况下我们可以简单地进行匹配。 century_name.

中不匹配任何波段的年份会导致 NA
century_bands2 <- with(century_bands, 
  stack(setNames(Map(seq, min_year, max_year), century_name)))
transform(x, century_name = with(century_bands2, ind[match(year, values)]))
##   year century_name
## 1 1730         18th
## 2 1860         19th
## 3 1941         20th
## 4 2011         21st

6a) 一个 tidyverse 的变体在很大程度上是这样的:

library(dplyr)
library(purrr)
library(tibble)
library(tidyr)

century_bands2 <- century_bands %>%
  { set_names(map2(.$min_year, .$max_year, seq), .$century_name) %>%
    as_tibble %>%
    pivot_longer(everything(), names_to = "century_name", values_to = "year")
  }

x %>% left_join(century_bands2, by = "year")
##   year century_name
## 1 1730         18th
## 2 1860         19th
## 3 1941         20th
## 4 2011         21st

7) case_when。我们可以将波段定义硬编码为 case_when:

library(dplyr)

x %>% mutate(century_name = case_when(
    year %in% 1700:1799 ~ "18th",
    year %in% 1800:1899 ~ "19th",
    year %in% 1900:1999 ~ "20th",
    year %in% 2000:2099 ~ "21st",
    TRUE ~ NA_character_))
##   year century_name
## 1 1730         18th
## 2 1860         19th
## 3 1941         20th
## 4 2011         21st

7a)case_when 表示的另一种方式是:

x %>% mutate(century_name = case_when(
    year < 1700 ~ NA_character_,
    year < 1800 ~ "18th",
    year < 1900 ~ "19th",
    year < 2000 ~ "20th",
    year < 2100 ~ "21st",
    TRUE ~ NA_character_))
##   year century_name
## 1 1730         18th
## 2 1860         19th
## 3 1941         20th
## 4 2011         21st