如何将 bigrams 拆分为 n 列的列和行对

How to split bigrams into column and row pairs for n-columns

假设这样一个数据框:

# example dataset
df <- data.frame(
         rowid = 1:3,
         a = c("ax","cz","by"),
         b = c("cy","ax","bz"),
         c = c("bz","ay","cx")
      )

实现以下转换的有效方法是什么?

#> # A tibble: 3 x 4
#>  rowid      a       b       c
#>  <int>  <chr>   <chr>   <chr>
#>      1      x       z       y
#>      2      x       y       z
#>      3      y       z       x

目标是获取每个双字母组的第二个字符,并将其排序到由第一个字符挑选出的列中,每一行。

如果可能,比较基础 R 和 Tidyverse 解决方案会很有用。

Tidyverse 解决方案的部分灵感来自于最近 post 使用 sjmisc 包中的 rotate_df()

df <- data.frame(
     rowid = 1:3,
     a = c("ax","cz","by"),
     b = c("cy","ax","bz"),
     c = c("bz","ay","cx")
  )

library(sjmisc)
df %>%
  # transpose the dataframe keeping column names
  rotate_df(cn=TRUE) %>%
  # sort columns by first character
  mutate(across(everything(),sort)) %>%
  # transpose back
  rotate_df() %>%
  # remove first character from each string
  mutate(across(everything(),~str_sub(.,2,-1))) %>%
  # make `rowid` column
  rownames_to_column(var="rowid")

可以选择使用 as_tibble() 将数据帧转换为 tibble 以完全匹配目标输出,给出:

#> # A tibble: 3 x 4
#>  rowid       a        b        c
#>  <int>   <chr>    <chr>    <chr>
#>      1       x        z        y
#>      2       x        y        z
#>      3       y        z        x

此解决方案将推广到 n-columns 并且 %>% 兼容。

由于您正在寻找 base 和 Tidyverse 的比较,我将提供一个 base 解决方案:

tdf <- t(df[-1])
tdf[] <- substr(tdf, 2, 2)[order(col(tdf), tdf)]
df[-1] <- t(tdf)

#  rowid a b c
#1     1 x z y
#2     2 x y z
#3     3 y z x

解释这 3 个步骤:

.1) 复制一份t()数据的转译版本
... .2b) 使用此命令从每个字符串的第二个字母开始 select 并覆盖 <- 转置数据。
.3) t()转回原始结构并覆盖<- df

中的数据

30K 行基准

基地:

bigdf <- df[rep(1:3,10000),]
bigdf$rowid <- 1:30000

system.time({
    tdf <- t(bigdf[-1])
    tdf[] <- substr(tdf,2,2)[order(col(tdf), tdf)]
    bigdf[-1] <- t(tdf)
})
##   user  system elapsed 
##  0.023   0.000   0.023 

整洁:

bigdf <- df[rep(1:3,10000),]
bigdf$rowid <- 1:30000

library(dplyr)
library(tibble)
library(sjmisc)
library(stringr)

system.time({
    bigdf %>%
        rotate_df(cn=TRUE) %>%
        mutate(across(everything(),sort)) %>%
        rotate_df() %>%
        mutate(across(everything(),~str_sub(.,2,-1))) %>%
        rownames_to_column(var="rowid")
})
##   user  system elapsed 
## 21.177   0.047  21.244