创建新列以指示列名在另一个字符串向量中的位置(使用 dplyr、purrr 和 stringr)
Create new columns to indicate column name's position inside another string vector (with dplyr, purrr, and stringr)
鉴于此示例数据:
require(stringr)
require(tidyverse)
labels <- c("foo", "bar", "baz")
n_rows <- 4
df <- 1:n_rows %>%
map(~ data.frame(
block_order=paste(sample(labels, size=length(labels), replace=FALSE),
collapse="|"))) %>%
bind_rows()
df
block_order
1 foo|bar|baz
2 baz|bar|foo
3 foo|baz|bar
4 foo|bar|baz
我想为 labels
中的每个字符串生成一列,它采用该字符串在每一行中以 |
分隔的序列中的位置值。
期望的输出:
block_order foo bar baz
1 foo|bar|baz 1 2 3
2 baz|bar|foo 3 2 1
3 foo|baz|bar 1 3 2
4 foo|bar|baz 1 2 3
我一直在 dplyr
/purrr
设置中尝试不同的变体,就像这个例子,我在 label
的每个值中 map
,然后尝试在 str_split
:
上使用 match
获得它在 block_order
中的位置
labels %>%
map(~ df %>%
transmute(!!.x := match(!!.x, str_split(block_order,
"\|",
simplify=TRUE)))) %>%
bind_cols(df, .)
但这会产生意外的输出:
block_order foo bar baz
1 foo|bar|baz 1 5 2
2 baz|bar|foo 1 5 2
3 foo|baz|bar 1 5 2
4 foo|bar|baz 1 5 2
我不太确定这些数字代表什么,或者为什么它们都一样。
如果有人能帮我弄清楚 (a) 如何在 dplyr
/purrr
框架中实现我想要的输出,以及 (b) 为什么这里提出的解决方案给出了它的输出,我将不胜感激。
我们可以将 'block_order' 拆分为 |
,使用 lapply
遍历 vector
的 list
,使用 [=17 获取索引=], rbind
vector
并将其分配给创建新列
labels <- c("foo", "bar", "baz")
df[labels] <- do.call(rbind, lapply(strsplit(df$block_order, "|",
fixed = TRUE), match, table = labels))
或与 tidyverse
类似的想法
library(tidyverse)
str_split(df$block_order, "[|]") %>%
map(~ .x %>%
match(table= labels)) %>%
do.call(rbind, .) %>%
as_tibble %>%
set_names(labels) %>%
bind_cols(df, .)
# block_order foo bar baz
#1 foo|bar|baz 1 2 3
#2 baz|bar|foo 3 2 1
#3 foo|baz|bar 1 3 2
#4 foo|bar|baz 1 2 3
另一种选择是使用 separate_rows
,将其重塑为 'long' 格式,然后 spread
返回
rownames_to_column(df, 'rn') %>%
separate_rows(block_order) %>%
group_by(rn) %>%
mutate(ind = match(block_order, labels), labels = factor(labels, levels = labels)) %>%
select(-block_order) %>%
spread(labels, ind) %>%
ungroup %>%
select(-rn) %>%
bind_cols(df, .)
我认为这可能有效:
library(tidyr)
library(purrr)
position_counter <- function(...) {
row = list(...)
row %>% map(~which(row == .)) %>% setNames(row)
}
df %>%
separate(block_order, labels) %>%
pmap_df(position_counter)
除非您出于其他原因需要,如果您只是为 labels
的每个值确定第一个匹配项的位置,则不必完全拆分字符串,regexpr
将给你。 map
ping labels
将为 labels
中的每个字符串提供一个包含一个元素的列表(因此这是一个快速迭代),然后您可以 pmap
rank
过来获取指数。使用 *_dfr
版本将结果简化为数据框并绑定到原始数据,
library(tidyverse)
set.seed(47)
labels <- c("foo", "bar", "baz")
df <- data_frame(block_order = replicate(10, paste(sample(labels), collapse = "|")))
labels %>%
map(~regexpr(.x, df$block_order)) %>%
pmap_dfr(~set_names(as.list(rank(c(...))), labels)) %>%
bind_cols(df, .)
#> # A tibble: 10 x 4
#> block_order foo bar baz
#> <chr> <dbl> <dbl> <dbl>
#> 1 baz|foo|bar 2. 3. 1.
#> 2 baz|bar|foo 3. 2. 1.
#> 3 bar|foo|baz 2. 1. 3.
#> 4 baz|foo|bar 2. 3. 1.
#> 5 foo|bar|baz 1. 2. 3.
#> 6 baz|foo|bar 2. 3. 1.
#> 7 foo|baz|bar 1. 3. 2.
#> 8 bar|baz|foo 3. 1. 2.
#> 9 baz|foo|bar 2. 3. 1.
#> 10 foo|bar|baz 1. 2. 3.
如果您更喜欢 stringr/stringi 而不是基础正则表达式,您可以通过在相同的安排中将 regexpr
调用更改为 str_locate(df$block_order, .x)[, "start"]
或 stringi::stri_locate_first_fixed
来实现相同的目的。
鉴于此示例数据:
require(stringr)
require(tidyverse)
labels <- c("foo", "bar", "baz")
n_rows <- 4
df <- 1:n_rows %>%
map(~ data.frame(
block_order=paste(sample(labels, size=length(labels), replace=FALSE),
collapse="|"))) %>%
bind_rows()
df
block_order
1 foo|bar|baz
2 baz|bar|foo
3 foo|baz|bar
4 foo|bar|baz
我想为 labels
中的每个字符串生成一列,它采用该字符串在每一行中以 |
分隔的序列中的位置值。
期望的输出:
block_order foo bar baz
1 foo|bar|baz 1 2 3
2 baz|bar|foo 3 2 1
3 foo|baz|bar 1 3 2
4 foo|bar|baz 1 2 3
我一直在 dplyr
/purrr
设置中尝试不同的变体,就像这个例子,我在 label
的每个值中 map
,然后尝试在 str_split
:
match
获得它在 block_order
中的位置
labels %>%
map(~ df %>%
transmute(!!.x := match(!!.x, str_split(block_order,
"\|",
simplify=TRUE)))) %>%
bind_cols(df, .)
但这会产生意外的输出:
block_order foo bar baz
1 foo|bar|baz 1 5 2
2 baz|bar|foo 1 5 2
3 foo|baz|bar 1 5 2
4 foo|bar|baz 1 5 2
我不太确定这些数字代表什么,或者为什么它们都一样。
如果有人能帮我弄清楚 (a) 如何在 dplyr
/purrr
框架中实现我想要的输出,以及 (b) 为什么这里提出的解决方案给出了它的输出,我将不胜感激。
我们可以将 'block_order' 拆分为 |
,使用 lapply
遍历 vector
的 list
,使用 [=17 获取索引=], rbind
vector
并将其分配给创建新列
labels <- c("foo", "bar", "baz")
df[labels] <- do.call(rbind, lapply(strsplit(df$block_order, "|",
fixed = TRUE), match, table = labels))
或与 tidyverse
library(tidyverse)
str_split(df$block_order, "[|]") %>%
map(~ .x %>%
match(table= labels)) %>%
do.call(rbind, .) %>%
as_tibble %>%
set_names(labels) %>%
bind_cols(df, .)
# block_order foo bar baz
#1 foo|bar|baz 1 2 3
#2 baz|bar|foo 3 2 1
#3 foo|baz|bar 1 3 2
#4 foo|bar|baz 1 2 3
另一种选择是使用 separate_rows
,将其重塑为 'long' 格式,然后 spread
返回
rownames_to_column(df, 'rn') %>%
separate_rows(block_order) %>%
group_by(rn) %>%
mutate(ind = match(block_order, labels), labels = factor(labels, levels = labels)) %>%
select(-block_order) %>%
spread(labels, ind) %>%
ungroup %>%
select(-rn) %>%
bind_cols(df, .)
我认为这可能有效:
library(tidyr)
library(purrr)
position_counter <- function(...) {
row = list(...)
row %>% map(~which(row == .)) %>% setNames(row)
}
df %>%
separate(block_order, labels) %>%
pmap_df(position_counter)
除非您出于其他原因需要,如果您只是为 labels
的每个值确定第一个匹配项的位置,则不必完全拆分字符串,regexpr
将给你。 map
ping labels
将为 labels
中的每个字符串提供一个包含一个元素的列表(因此这是一个快速迭代),然后您可以 pmap
rank
过来获取指数。使用 *_dfr
版本将结果简化为数据框并绑定到原始数据,
library(tidyverse)
set.seed(47)
labels <- c("foo", "bar", "baz")
df <- data_frame(block_order = replicate(10, paste(sample(labels), collapse = "|")))
labels %>%
map(~regexpr(.x, df$block_order)) %>%
pmap_dfr(~set_names(as.list(rank(c(...))), labels)) %>%
bind_cols(df, .)
#> # A tibble: 10 x 4
#> block_order foo bar baz
#> <chr> <dbl> <dbl> <dbl>
#> 1 baz|foo|bar 2. 3. 1.
#> 2 baz|bar|foo 3. 2. 1.
#> 3 bar|foo|baz 2. 1. 3.
#> 4 baz|foo|bar 2. 3. 1.
#> 5 foo|bar|baz 1. 2. 3.
#> 6 baz|foo|bar 2. 3. 1.
#> 7 foo|baz|bar 1. 3. 2.
#> 8 bar|baz|foo 3. 1. 2.
#> 9 baz|foo|bar 2. 3. 1.
#> 10 foo|bar|baz 1. 2. 3.
如果您更喜欢 stringr/stringi 而不是基础正则表达式,您可以通过在相同的安排中将 regexpr
调用更改为 str_locate(df$block_order, .x)[, "start"]
或 stringi::stri_locate_first_fixed
来实现相同的目的。