将宽格式转换为长格式,然后嵌套列

Posted

技术标签:

【中文标题】将宽格式转换为长格式,然后嵌套列【英文标题】:Pivoting wide to long format and then nesting columns 【发布时间】:2021-04-09 19:54:35 【问题描述】:

我收到了多种格式的数据。每一行都与当前表外部的一个变量以及与该变量相关的可能值有关。我正在尝试:(1)转为长格式,(2)嵌套转轴值。

示例

library(tibble)

df_1 <-
  tribble(~key, ~values.male, ~values.female, ~values.red, ~values.green, ~value,
        "gender", 0.5, 0.5, NA, NA, NA,
        "age", NA, NA, NA, NA, "50",
        "color", NA, NA, TRUE, FALSE, NA,
        "time_of_day", NA, NA, NA, NA, "noon")

## # A tibble: 4 x 6
##   key         values.male values.female values.red values.green value
##   <chr>             <dbl>         <dbl> <lgl>      <lgl>        <chr>
## 1 gender              0.5           0.5 NA         NA           NA   
## 2 age                NA            NA   NA         NA           50   
## 3 color              NA            NA   TRUE       FALSE        NA   
## 4 time_of_day        NA            NA   NA         NA           noon 

在这个例子中,我们看到gender 可以有female = 0.5male = 0.5。另一方面,age 只能有一个值50。从第 3 行我们了解到color 可以具有red = TRUEgreen = FALSEtime_of_day = noon 的值。

因此,透视表应采用以下嵌套形式:

my_pivoted_df <-
  structure(
    list(
      var_name = c("gender", "age", "color", "time_of_day"),
      vals = list(
        structure(
          list(
            level = c("male", "female"),
            value = c(0.5,
                      0.5)
          ),
          row.names = c(NA, -2L),
          class = c("tbl_df", "tbl", "data.frame")
        ),
        "50",
        structure(
          list(
            level = c("red", "green"),
            value = c(TRUE,
                      FALSE)
          ),
          row.names = c(NA, -2L),
          class = c("tbl_df", "tbl", "data.frame")
        ),
        "noon"
      )
    ),
    row.names = c(NA, -4L),
    class = c("tbl_df", "tbl",
              "data.frame")
  )


## # A tibble: 4 x 2
##   var_name    vals            
##   <chr>       <list>          
## 1 gender      <tibble [2 x 2]>
## 2 age         <chr [1]>       
## 3 color       <tibble [2 x 2]>
## 4 time_of_day <chr [1]>

我试图解决这个问题

df_1 存在一些问题。首先,列的当前命名不方便。 value 之类的标头并不理想,因为它们与 pivot_longer()".value" 机制冲突。其次,df_1values(复数形式)当key 有多个选项时(例如,color 的“红色”和“绿色”),但value(单数)只有一个时key 的选项(例如 age)。 下面是我失败的代码,灵感来自this answer。

library(tidyr)
library(dplyr)

df_1 %>%
  rename_with( ~ paste(.x, "single", sep = "."), .cols = value) %>% ## changed the header because otherwise it breaks
  pivot_longer(cols = starts_with("val"),
               names_to = c("whatevs", ".value"), names_sep = "\\.")


## # A tibble: 8 x 7
##   key         whatevs  male female red   green single
##   <chr>       <chr>   <dbl>  <dbl> <lgl> <lgl> <chr> 
## 1 gender      values    0.5    0.5 NA    NA    NA    
## 2 gender      value    NA     NA   NA    NA    NA    
## 3 age         values   NA     NA   NA    NA    NA    
## 4 age         value    NA     NA   NA    NA    50    
## 5 color       values   NA     NA   TRUE  FALSE NA    
## 6 color       value    NA     NA   NA    NA    NA    
## 7 time_of_day values   NA     NA   NA    NA    NA    
## 8 time_of_day value    NA     NA   NA    NA    noon  

我缺乏一些技巧来解决这个问题。

【问题讨论】:

【参考方案1】:

实现所需结果的 tidyverse 方法可能如下所示:

library(tibble)

df_1 <-
  tribble(~key, ~values.male, ~values.female, ~values.red, ~values.green, ~value,
          "gender", 0.5, 0.5, NA, NA, NA,
          "age", NA, NA, NA, NA, "50",
          "color", NA, NA, TRUE, FALSE, NA,
          "time_of_day", NA, NA, NA, NA, "noon")

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

df_pivoted <- df_1 %>% 
  mutate(across(everything(), as.character)) %>% 
  pivot_longer(-key, names_to = "level", names_prefix = "^values\\.", values_drop_na = TRUE) %>% 
  group_by(key) %>% 
  nest() %>% 
  mutate(data = map(data, ~ if (all(.x$level == "value")) deframe(.x) else .x))
df_pivoted
#> # A tibble: 4 x 2
#> # Groups:   key [4]
#>   key         data            
#>   <chr>       <list>          
#> 1 gender      <tibble [2 × 2]>
#> 2 age         <chr [1]>       
#> 3 color       <tibble [2 × 2]>
#> 4 time_of_day <chr [1]>

编辑在您的 cmets 中对所需结果的澄清之后,我们可以简单地摆脱 map 语句作为结尾(这基本上是为了将没有级别的类别的小标题转换为向量)并在嵌套之前添加一个 mutate 语句,用 NA 替换没有level 的类别的级别:

pivot_nest <- function(x) 
  mutate(x, across(everything(), as.character)) %>% 
    pivot_longer(-key, names_to = "level", names_prefix = "^values\\.", values_drop_na = TRUE) %>% 
    group_by(key) %>% 
    mutate(level = ifelse(all(level == "value"), NA_character_, level)) %>% 
    nest() 


df_pivoted <- df_1 %>% 
  pivot_nest()
df_pivoted
#> # A tibble: 4 x 2
#> # Groups:   key [4]
#>   key         data            
#>   <chr>       <list>          
#> 1 gender      <tibble [2 × 2]>
#> 2 age         <tibble [1 × 2]>
#> 3 color       <tibble [2 × 2]>
#> 4 time_of_day <tibble [1 × 2]>
df_pivoted$data
#> [[1]]
#> # A tibble: 2 x 2
#>   level value
#>   <chr> <chr>
#> 1 male  0.5  
#> 2 male  0.5  
#> 
#> [[2]]
#> # A tibble: 1 x 2
#>   level value
#>   <chr> <chr>
#> 1 <NA>  50   
#> 
#> [[3]]
#> # A tibble: 2 x 2
#>   level value
#>   <chr> <chr>
#> 1 red   TRUE 
#> 2 red   FALSE
#> 
#> [[4]]
#> # A tibble: 1 x 2
#>   level value
#>   <chr> <chr>
#> 1 <NA>  noon

df_2 <- tribble(~key, ~value, "age", "50", "income", "100000", "time_of_day", "noon")

df_pivoted2 <- df_2 %>% 
  pivot_nest()
df_pivoted2
#> # A tibble: 3 x 2
#> # Groups:   key [3]
#>   key         data            
#>   <chr>       <list>          
#> 1 age         <tibble [1 × 2]>
#> 2 income      <tibble [1 × 2]>
#> 3 time_of_day <tibble [1 × 2]>
df_pivoted2$data
#> [[1]]
#> # A tibble: 1 x 2
#>   level value
#>   <chr> <chr>
#> 1 <NA>  50   
#> 
#> [[2]]
#> # A tibble: 1 x 2
#>   level value 
#>   <chr> <chr> 
#> 1 <NA>  100000
#> 
#> [[3]]
#> # A tibble: 1 x 2
#>   level value
#>   <chr> <chr>
#> 1 <NA>  noon

【讨论】:

谢谢!有没有办法组织输出(df_pivoted),使其data 列不存在?相反,data 下的值将位于value 列中。我在想也许使用dplyr::coalesce() 作为最后一步可以解决问题,但我犹豫了。如果我只有单个值,例如 df_2 &lt;- tribble(~key, ~value, "age", "50", "income", "100000", "time_of_day", "noon"),这可能会中断 我上面的评论还揭示了我在帖子中没有提到的情况。如果所有值都是单一的,例如评论中的 df_2 怎么办?在我的真实数据中,这种情况经常发生。那么输出在列名(keydata)方面会有所不同,而不是 df_1 场景(keylevelvaluedata 在未嵌套的输出格式中) .如何确保输出始终只有keyvalue 列,如果需要,还有额外的level 列? 我找到了一些解决方案:df_pivoted %&gt;% unnest(data) %&gt;% if(all(c("data", "value") %in% colnames(.))) (mutate(., value = coalesce(data, value)) %&gt;% select(-data)) else . %&gt;% nest()。但我认为它不那么可读,也许不是最佳编码实践。如果有更简单/更清洁的解决方案,我会很高兴。谢谢! 嗨,艾曼。不确定我是否正确。但是看看我的编辑。基本上我不认为我们需要 unnest + ... + nest 来获得你想要的结果。【参考方案2】:

一个选项将返回与提供的输入相同类型的输出:

df_1 %>%
 group_split(key) %>%
 map_dfr(~ select(., where(~ !all(is.na(.)))) %>%
          pivot_longer(-key, names_to = "level", names_prefix = "^values\\.") %>%
          summarise(key = first(key),
                    vals = if(n() == 1) list(value) else list(tibble(level, value))))

  key         vals            
  <chr>       <list>          
1 age         <chr [1]>       
2 color       <tibble [2 × 2]>
3 gender      <tibble [2 × 2]>
4 time_of_day <chr [1]>  

输出结构:

$ key : chr [1:4] "age" "color" "gender" "time_of_day"
 $ vals:List of 4
  ..$ : chr "50"
  ..$ : tibble [2 × 2] (S3: tbl_df/tbl/data.frame)
  .. ..$ level: chr [1:2] "red" "green"
  .. ..$ value: logi [1:2] TRUE FALSE
  ..$ : tibble [2 × 2] (S3: tbl_df/tbl/data.frame)
  .. ..$ level: chr [1:2] "male" "female"
  .. ..$ value: num [1:2] 0.5 0.5
  ..$ : chr "noon"

【讨论】:

【参考方案3】:

这是一个data.table 解决方案,因为我更喜欢meltdcast,但应该可以轻松转移到dplyr

library(data.table)
df <- setDT(df_1)

plouf <- melt(df,measure.vars = patterns("value")) %>%
  .[!is.na(value),.(key,level = gsub("values.","",variable),value)] 

这给出了:

           key  level value
1:      gender   male   0.5
2:      gender female   0.5
3:       color    red  TRUE
4:       color  green FALSE
5:         age  value    50
6: time_of_day  value  noon

您现在可以遍历唯一的 key 值以输出您想要的内容:

keylist <- unique(plouf$key)
result <- tibble(varname = keylist,
               vals = lapply(keylist,function(x)
                 if(plouf[x == key,level[1]] != "value")
                   plouf[x == key,.(level,value)]
                 else
                   plouf[x == key,value]
                 
               )
               
)

在这里您可以获得嵌套的 tibble(其中包含 data.tables 和字符)

【讨论】:

以上是关于将宽格式转换为长格式,然后嵌套列的主要内容,如果未能解决你的问题,请参考以下文章

使用 INNER JOIN LATERAL 和 postgresql 将宽表转换为长表

R语言ggplot2可视化:应用pivot_longer函数将数据从宽格式转换为长格式为dataframe的每一列绘制密度图和直方图(堆叠)

将值列表添加到行,然后将数据框转换为长格式

R从短格式转换为长格式,以简短形式计数[重复]

R语言配对图(pair plot)可视化:pivot_longer函数将宽格式的数据重塑为长格式并进行数据全连接(full join)可视化基本的配对图(pair plot)

R语言配对图(pair plot)可视化:pivot_longer函数将宽格式的数据重塑为长格式并进行数据全连接(full join)可视化基本的配对图(pair plot)