【问题标题】:Iterating through a multi-dimensional dataset in R遍历 R 中的多维数据集
【发布时间】:2017-04-12 22:08:54
【问题描述】:

我不擅长循环和 [l,s,v] 应用,我需要总结一个大型纵向数据集。我已经彻底搜索了我的问题几天,我发布这个是因为我无法解决我的问题。

数据看起来像这样:

id var1_dose var1_unit var2_dose var2_unit var3_dose var3_unit
1         2        mL         5        mL         1        mL
2         4        mg         2        mg         4        mg
3         6       mcg         4       mcg         2       mcg
1         1        mL         1        mL         3        mL
2         3        mL         3        mL         5        mL

问题 1

我想使用 apply 或循环追加 var(1-3) 剂量并作为列表输出。

问题 2

我想创建一个新变量以通过数字连接剂量和单位,例如在第 1 行,var1 = 2mL,var2 = 5 mL,var3 = 1mL

非常感谢您的帮助。

【问题讨论】:

标签: r loops variables iteration apply


【解决方案1】:

这不是您要求的答案,但我认为它对您和其他人都有帮助。请考虑使用 tidying 您的数据框。例如:

library(tidyr)

df1 <- data.frame(id = c(1,2,3,1,2),
       var1_dose = c(2,4,6,1,3),
       var1_unit = c("mL", "mg", "mcg", "mL", "mL"),
       var2_dose = c(5,2,4,1,3),
       var2_unit = c("mL", "mg", "mcg", "mL", "mL"),
       var3_dose = c(1,4,2,3,5),
       var3_unit = c("mL", "mg", "mcg", "mL", "mL"),
       stringsAsFactors = FALSE)

df1.gather <- gather(df1, variable, value, -id)
df1.tidy <- separate(df1.gather, variable,
            into = c("variable", "measurement"), sep = "_")

head(df1.tidy)

# id variable measurement value
# 1      var1        dose     2
# 2      var1        dose     4
# 3      var1        dose     6
# 1      var1        dose     1
# 2      var1        dose     3
# 1      var1        unit    mL
# 2      var1        unit    mg
# 3      var1        unit   mcg
# 1      var1        unit    mL
# 2      var1        unit    mL

# hacky workaround to get a units column
df1.tidy <- subset(df1.tidy, measurement != "unit")
df1.tidy$unit <- rep(c("mL", "mg", "mcg", "mL", "mL"), 3)

这种结构应该使总结、建模和绘图(使用 ggplot2)变得更加容易。

【讨论】:

  • 我认为在这种情况下保留剂量和单位两个单独的列更有意义。这使得他可以很容易地连接列以获得每个变量的剂量 + 单位。较长的格式并不容易。
  • 是的,值栏最好不要有“units for units”;那是我的第一次基本尝试!我会完善它并稍后编辑。
  • 更简单的结果,更复杂的路径:library(tidyverse) ; df1 %&gt;% gather(var_dose, dose, matches('dose')) %&gt;% gather(var_unit, unit, matches('unit')) %&gt;% mutate_at(vars(matches('var')), parse_number) %&gt;% filter(var_dose == var_unit) %&gt;% select(var = var_dose, everything(), -var_unit) 或窃取 data.table::melt 的多收集功能,直到 Hadley 在 tidyr 中实现它:library(data.table) ; melt(setDT(df1), id = 1, measure.vars = patterns('var._dose', 'var._unit'), variable.factor = FALSE, value.name = c('dose', 'unit'))[, variable := as.integer(variable)][]
  • @alistaire 这不是评论,而是答案:)
  • 据我了解,Hadley 希望人们进行多重收集的方式(例如使用 base-r reshape)是首先将 gather 所有列转换为一个非常长的格式 df。然后使用separate/spread 将它们拆分得更宽一些。这是他的基本 unix 哲学的一部分“每个函数都应该做一件简单的事情并做好”。 Alistaire 的 2x partial gathers 方法也有效,但需要过滤在匹配变量上批处理的行。使用两个连续的gathers 导致“笛卡尔连接”出现问题。
【解决方案2】:

也许这有帮助

v1 <- unique(sub("_.*", "", names(df1)[-1]))
df1[paste0(v1, "dose_unit")] <- lapply(v1, function(x)
                         do.call(paste0, df1[grep(x, names(df1))]))
df1
#  id var1_dose var1_unit var2_dose var2_unit var3_dose var3_unit var1dose_unit var2dose_unit var3dose_unit
#1  1         2        mL         5        mL         1        mL           2mL           5mL           1mL
#2  2         4        mg         2        mg         4        mg           4mg           2mg           4mg
#3  3         6       mcg         4       mcg         2       mcg          6mcg          4mcg          2mcg
#4  1         1        mL         1        mL         3        mL           1mL           1mL           3mL
#5  2         3        mL         3        mL         5        mL           3mL           3mL           5mL

【讨论】:

  • 非常感谢!我有一个关于“_”的问题。代替。我在使用下划线时遇到了一些问题,我想知道它是如何影响循环的。如果你能提供一些启示,那就太好了。
  • @kquach 我只是将它自动化以获得 'var1', 'var2', 'var3' ,以防万一你有很多变量。这个想法是匹配_ 后跟任何字符并将其替换为“”。因此,该部分从名称中剥离以创建向量 v1.. 在这里,我们循环 'v1' 的每个元素,然后用 grep 查找与其匹配的列,因为我们需要为每个元素粘贴带有剂量的单位vars,这是子集
【解决方案3】:

@akrun 给出了正确答案。如果您希望将结果作为列表-

df <- data.frame(id = c(1,2,3,1,2), var1_dose = c(2,4,6,1,3),
             var1_unit = c("mL","mg","mcg","mL","mL"), var2_dose = c(5,2,4,1,3),
             var2_unit = c("mL","mg","mcg","mL","mL"), var3_dose = c(1,4,2,3,5),
             var3_unit = c("mL","mg","mcg","mL","mL"))

dose_list <- lapply(seq(2,ncol(df)-1,2), function(x) paste0(df[, x],df[, x + 1]))
names(dose_list) <- c(paste0("dose_",seq(1:(ncol(df) / 2))))

【讨论】:

    【解决方案4】:

    为了扩展我对@neilfws 答案的评论(并使用他的样本数据),您的数据最好采用较长的形式,这将使您的其余分析更加容易。但是,您的数据目前采用宽格式,因此您有两组需要收集(融合)的列,这比平均从宽到长的重塑需要更多的工作。

    一种选择是收集所有内容,然后散布回广泛的地方。这很好用,有一个问题是 tidyr::spread 对索引非常特别,因此您必须添加第二个 ID 列来标识结果的行,并且要花一点时间来制作该列。

    library(tidyverse)
    
    df1_tidy <- df1 %>% 
        gather(var, val, -id) %>%    # gather everything to long form
        separate(var, c('var', 'var2')) %>%    # separate "var*" from dose/unit
        group_by(var2) %>% 
        mutate(var = parse_number(var),    # extract var to integer
               id2 = seq(n())) %>%    # add ID column for spreading
        spread(var2, val, convert = TRUE) %>% 
        select(-id2)    # cleanup
    
    ## # A tibble: 15 × 4
    ##       id   var  dose  unit
    ## *  <dbl> <dbl> <int> <chr>
    ## 1      1     1     2    mL
    ## 2      1     1     1    mL
    ## 3      1     2     5    mL
    ## 4      1     2     1    mL
    ## 5      1     3     1    mL
    ## 6      1     3     3    mL
    ## 7      2     1     4    mg
    ## 8      2     1     3    mL
    ## 9      2     2     2    mg
    ## 10     2     2     3    mL
    ## 11     2     3     4    mg
    ## 12     2     3     5    mL
    ## 13     3     1     6   mcg
    ## 14     3     2     4   mcg
    ## 15     3     3     2   mcg
    

    或者,您可以单独设置gather。这种方法的问题是它会给你不想要的组合(var1 和 var3 等),所以你必须filter 回到原来的位置。

    df1_tidy <- df1 %>% 
        gather(var, dose, contains('dose')) %>% 
        gather(var_unit, unit, contains('unit')) %>% 
        mutate_at(vars(contains('var')), parse_number) %>%    # extract var numbers
        filter(var == var_unit) %>%    # filter to matching combinations
        select(-var_unit)    # cleanup
    
    df1_tidy
    ##    id var dose unit
    ## 1   1   1    2   mL
    ## 2   2   1    4   mg
    ## 3   3   1    6  mcg
    ## 4   1   1    1   mL
    ## 5   2   1    3   mL
    ## 6   1   2    5   mL
    ## 7   2   2    2   mg
    ## 8   3   2    4  mcg
    ## 9   1   2    1   mL
    ## 10  2   2    3   mL
    ## 11  1   3    1   mL
    ## 12  2   3    4   mg
    ## 13  3   3    2  mcg
    ## 14  1   3    3   mL
    ## 15  2   3    5   mL
    

    虽然 tidyr (yet) 没有多重收集功能,但需要使用上述方法,但 data.table 的 melt 版本允许您传递其 measure.vars 参数正则表达式模式,从而启用多重收集。语法看起来很不一样,有不同的东西要清理,但它会把你带到同一个地方:

    library(data.table)
    
    dt1 <- melt(setDT(df1), 
                measure.vars = patterns('dose', 'unit'),    # set gathering patterns
                variable.factor = FALSE,    # because factor numbers are evil
                value.name = c('dose', 'unit'))    # set column names
    
    dt1 <- dt1[, variable := as.integer(variable)][]    # cleanup
    
    dt1
    ##     id variable dose unit
    ##  1:  1        1    2   mL
    ##  2:  2        1    4   mg
    ##  3:  3        1    6  mcg
    ##  4:  1        1    1   mL
    ##  5:  2        1    3   mL
    ##  6:  1        2    5   mL
    ##  7:  2        2    2   mg
    ##  8:  3        2    4  mcg
    ##  9:  1        2    1   mL
    ## 10:  2        2    3   mL
    ## 11:  1        3    1   mL
    ## 12:  2        3    4   mg
    ## 13:  3        3    2  mcg
    ## 14:  1        3    3   mL
    ## 15:  2        3    5   mL
    

    无论您选择哪种方法,一旦您的数据整理好,组合doseunit 就很容易:

    # base R
    df1_tidy$dose_unit <- paste0(df1_tidy$dose, df1_tidy$unit)
    
    # tidyverse
    df1_tidy <- df1_tidy %>% mutate(dose_unit = paste0(dose, unit))
    
    # data.table
    dt1 <- dt1[, dose_unit := paste0(dose, unit)][]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-12-18
      • 2016-01-23
      • 2012-04-16
      • 2023-02-21
      相关资源
      最近更新 更多