【问题标题】:Add column for each unique value in given row为给定行中的每个唯一值添加列
【发布时间】:2021-11-30 19:46:28
【问题描述】:

我正在尝试将当前数据集的格式更改为每行有 1 个用户的格式,并将 Color 和 Food 列中的所有唯一值(值的动态数量)拆分为它们自己的列,其中 Yes 和不,每个用户都有一个唯一的 ID。

Current format: 
ID | Name  | Color  | Food 
1  | John  | Blue   | Pizza
1  | John  | Red    | Pizza
1  | John  | Yellow | Pizza
1  | John  | Blue   | Ice Cream
1  | John  | Red    | Ice Cream
1  | John  | Yellow | Ice Cream
2  | Kelly | Blue   | Pizza
2  | Kelly | Red    | Pizza


Desired format: 
ID | Name  | Color_Blue | Color_Red | Color_Yellow | Food_Pizza | Food_Ice Cream |
1  | John  | Yes        | Yes       | Yes          | Yes        | Yes            |
2  | Kelly | Yes        | Yes       | No           | Yes        | No             |

【问题讨论】:

    标签: r csv dplyr


    【解决方案1】:
    library(dplyr); library(tidyr)
    df %>% 
      pivot_longer(-c(ID:Name)) %>%
      unite("col", c(name, value)) %>%
      distinct(ID, Name, col) %>%
      mutate(val = "Yes") %>%
      pivot_wider(names_from = col, values_from = "val", values_fill = "No")
    
    # A tibble: 2 x 7
      ID    Name  Color_Blue Food_Pizza Color_Red Color_Yellow `Food_Ice Cream`
      <chr> <chr> <chr>      <chr>      <chr>     <chr>        <chr>           
    1 1     John  Yes        Yes        Yes       Yes          Yes             
    2 2     Kelly Yes        Yes        Yes       No           No   
    

    如果您想要一个基本的 R 等效项,这里是使用相同步骤的一个。 (有人可以帮我弄清楚如何删除行名和附加到最终列名的“val.”吗?)

    df2 <- reshape(df, 
            direction = "long", 
            varying = c("Color", "Food"),
            v.names = "Value",
            timevar = "col_name",
            times = c("Color", "Food"))
    df2$col = paste(df2$col_name, df2$Value, sep = "_")
    
    df3 <- unique(df2[c("ID", "Name", "col")])
    df3$val = "Yes"
    
    df4 <- reshape(df3,
                   direction = "wide",
                   idvar = c("ID", "Name"),
                   timevar = "col")
    df4[is.na(df4)] <- "No"
    
    > df4
            ID  Name val.Color_Blue val.Color_Red val.Color_Yellow val.Food_Pizza val.Food_Ice Cream
    1.Color  1  John            Yes           Yes              Yes            Yes                Yes
    7.Color  2 Kelly            Yes           Yes               No            Yes                 No
    

    样本数据

    df <- tribble(~ID , ~Name  , ~Color  , ~Food,
    "1"  , "John",  "Blue",    "Pizza",
    "1"  , "John" , "Red",    "Pizza",
    "1"  , "John",  "Yellow",  "Pizza",
    "1"  , "John" , "Blue",   "Ice Cream",
    "1"  , "John",  "Red",    "Ice Cream",
    "1"  , "John" , "Yellow", "Ice Cream",
    "2"  , "Kelly", "Blue",    "Pizza",
    "2"  , "Kelly", "Red",    "Pizza")
      
    

    【讨论】:

      【解决方案2】:

      清理了基础 R 脚本:

      # Data to import: df => data.frame
      df <- structure(list(ID = c(1L, 1L, 1L, 1L, 1L, 1L, 2L, 2L), Name = c("John", 
      "John", "John", "John", "John", "John", "Kelly", "Kelly"), Color = c("Blue", 
      "Red", "Yellow", "Blue", "Red", "Yellow", "Blue", "Red"), Food = c("Pizza", 
      "Pizza", "Pizza", "Ice Cream", "Ice Cream", "Ice Cream", "Pizza", 
      "Pizza")), class = "data.frame", row.names = c(NA, -8L))
      
      # Function to extract the column names of data.frame 
      # not contained in a character vector: 
      # resolve_other_vec_names => function
      resolve_other_vec_names <- function(df, vec_names){
        # Explicitly define returned object: character vector => env
        return(
          colnames(df)[!(
            colnames(df) %in% vec_names
            )
          ]
        )
      }
      
      # Create a formula to aggregate a data.frame by:
      # resolve_agg_formula => function()
      resolve_agg_formula <- function(keep_vecs){
        # Formula object to aggregate data.frame by:
        # res => formula object
        res <- as.formula(
          paste(
            ".", 
            paste0(
              keep_vecs,
              collapse = "+"
            ),
            sep = "~"
          )
        )
        # Explicitly define returned object: formula => env
        return(res)
      }
      
      # Function required to aggregate vector by:
      # agg_func => function
      agg_func <- function(df, agg_formula){
        # Function to agg by: .agg_vec_by => function
        .agg_vec_by <- function(x){
          ifelse(
            any(x),
            "Yes",
            "No"
          )
        }
        
        # Aggregate data.frame: res => data.frame 
        res <- aggregate(
          agg_formula, 
          df,
          FUN = .agg_vec_by
        )
        
        # Explicitly define the returned object:
        # data.frame => env
        return(res)
      }
      
      # Function to spread a data.frame's vector, 
      # from unique row-values to column vectors:
      # spread_func => function()
      spread_func <- function(df, vec_name){
        # Extract the unique values of a given vector:
        # y => vector
        y <- unique(df[,vec_name])
        
        # Determine if a row contains a given value in y:
        # row_contains_value_df => boolean data.frame
        row_contains_value_df <- data.frame(
          outer(
            df[,vec_name], 
            y,
            `==`
          ),
          row.names = NULL
        )
        
        # Create the data.frame vector names: 
        # df_vec_names => character vector
        df_vec_names <- paste(
          vec_name,
          y,
          sep = "_"
        )
        
        # Rename the data.frame vectors: res => data.frame
        res <- setNames(
          row_contains_value_df,
          df_vec_names
        )
        
        # Explicitly define the returned object: data.frame => env
        return(res)
      }
      
      # Function to combine list of data.frames into df: 
      # df_list_2_df => function
      df_list_2_df <- function(df_list, cmb_func = c(rbind, cbind)){
        # Resolve the desired combination function: 
        # cmb_func_resolved => character scalar
        cmb_func_resolved <- match.fun(cmb_func)
        # Combine list of data.frames into a data.frame 
        # using a given combination function: res => data.frame
        res <- data.frame(
          do.call(
            cmb_func_resolved,
            df_list
          ),
          row.names = NULL
        )
        # Explicitly define the returned object: 
        # data.frame => Env
        return(res)
      }
      
      # Define the main function: main => function
      main <- function(){
        # Vectors to spread values to columns: 
        # spread_vecs => character vector
        spread_vecs <- c("Color", "Food")
        
        # Vectors to keep as columns: keep_vecs => character vector
        keep_vecs <- resolve_other_vec_names(df, spread_vecs)
        
        # Formula to aggregate the data.frame by: 
        # agg_formula => formula object
        agg_formula <- resolve_agg_formula(keep_vecs)
        
        # Resolve if person/id has observed value: 
        # res => data.frame
        res <- agg_func(
          cbind(
            df[,keep_vecs],
            df_list_2_df(
              lapply(
                spread_vecs,
                function(x){
                  spread_func(df, x)
                }
              ),
              cbind
            )
          ),
          agg_formula
        )
        
        # Print data.frame to console: data.frame => stdout(console)
        res
        
      }
      
      # Execute main if called:
      if (sys.nframe() == 0){
        # Execute the main function: data.frame => stdout(console)
        main()
      }
      

      【讨论】:

      • 我不想废话你的代码,但这就是人们发明 tidyverse 的原因。
      • @user438383 好的,如果您曾经在不允许安装软件包的环境中工作并且您想使用 R,则需要学习 Base R。另一件事:softwareengineering.stackexchange.com/a/380026/391730
      • 对不起,我不是故意的。当然,我理解这两点。这与行数无关,而是 base-r 强制您编写的高度嵌套的样式是多么不直观。我可以盯着它看了很长时间,不知道它是什么意思。我希望这里似乎有一种情况,人们尝试在一个巨大的嵌套语句中做所有事情,而不是把它分成多个部分,这对于 R 的新手来说一定很难学习。
      • @user438383 不用担心。我理解 tidyverse 的论点,特别是与 monadic 代码的好处有关(即更具可读性,没有那么多中间结果对象等),甚至我自己也使用 tidyverse。还有其他问题与需要从 CRAN 安装(这通常违反安全策略)、将语言拆分为不同的语言、弃用限制代码生命周期的功能以及性能(通常比 base / data.table 差) -- 在这种情况下不适用)。所以我一般会提供基本的 R 解决方案。
      • @user438383 无论如何,听取了您的建议并清理了脚本,并提供了一些注释来解释发生了什么。
      猜你喜欢
      • 1970-01-01
      • 2015-09-03
      • 1970-01-01
      • 2019-04-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多