匹配数据框中所有列的字符串并估算或替换值

Match strings across all columns in dataframe and impute or replace values

问题

我有一个数据框,df,有 82 列,在第四列之后,其余列包含相同的起始字符串,一式三份。例如。 mass.mean, mass.stdev, mass.rsd, density.mean, density.stdev, density.rsd 等等 我需要:

1) 将一式三份的列与部分字符串(例如 massdensity)和

匹配

2) 如果满足条件,则用特定计算替换那些匹配列的值(例如,如果 df 中的列包含字符串 mass,则将 NA 替换为先前的值(估算)zoo::na.locf 或者如果 df 中的列包含字符串 density 则将 NA 替换为零。

对我来说,我似乎需要合并 grepl lapply 和一个 ifelse 梯子,但我似乎无法将它们组合在一起。如果我可以避免从宽转换为长,那将是最好的,因为我的数据框包含 > 450k 行。

示例数据框

set.seed(123)
df <- data.frame("A" = sample(0:100,8), 
             "B" = sample(0:100,8),
             "C" = sample(0:100,8), 
             "D" = sample(0:100,8),
             "mass.mean" = c(1, NA, 2, 3, NA, NA, 2, 1), 
             "mass.stdev" = c(1, NA, 1, 1, NA, NA, 2, 1),
             "mass.rsd" = c(0, NA, 0.1, 0.1, NA, NA, 0.2, 0.1), 
             "denisty.mean" = c(6, 5, 7, NA, NA, NA, 6, 4), 
             "denisty.stdev" = c(3, 1, 1, NA, NA, NA, 2, 1),
             "denisty.rsd" = c(0.8,0.2, 2, NA, NA, NA, 0.5, 0.7),
             stringsAsFactors = FALSE)

print(df)

   A  B  C  D mass.mean mass.stdev mass.rsd denisty.mean denisty.stdev denisty.rsd
1 29 55 24 66         1          1      0.0            6             3         0.8
2 78 45  4 70        NA         NA       NA            5             1         0.2
3 40 94 32 53         2          1      0.1            7             1         2.0
4 86 44 93 58         3          1      0.1           NA            NA          NA
5 91 65 86 28        NA         NA       NA           NA            NA          NA
6  4 54 66 14        NA         NA       NA           NA            NA          NA
7 50  9 60 91         2          2      0.2            6             2         0.5
8 83 84 97 84         1          1      0.1            4             1         0.7

期望输出

   A  B  C  D mass.mean mass.stdev mass.rsd denisty.mean denisty.stdev denisty.rsd
1 29 55 24 66         1          1      0.0            6             3         0.8
2 78 45  4 70         1          1      0.0            5             1         0.2
3 40 94 32 53         2          1      0.1            7             1         2.0
4 86 44 93 58         3          1      0.1            0             0         0.0
5 91 65 86 28         3          1      0.1            0             0         0.0
6  4 54 66 14         3          1      0.1            0             0         0.0
7 50  9 60 91         2          2      0.2            6             2         0.5
8 83 84 97 84         1          1      0.1            4             1         0.7

像这样应该可以解决您的 'density' 列问题:

library(dplyr)
df %>% 
    mutate_at(vars(starts_with("density")),function(x) {if_else(is.na(x),0,x)})

'mass' 有点棘手,因为您必须获得以前的值,而且您似乎想从上次有一个 non-NA 值的时间开始进行估算。这个解决方案通过保留 NA 来处理第一行包含 NA 的情况,因为我不确定你想要发生什么。

imputePrev <- function(x) {
    l <- seq_along(x) # declare vector of appropriate length rather than growing
    for (i in seq_along(x)){
        if (i == 1){
            l[i] <- x[i] # always keep the first row
            next
        } else if (is.na(x[i])){
            for (j in 1:(i-1)) { # get the last non-NA value if one is available
                if (!is.na(x[i-j])){
                    l[i] <- x[i-j]
                    break
                }
            }
        } else {
            l[i] <- x[i]
        }
    }
    return(l)
}

df %>%  mutate_at(vars(starts_with("mass")),imputePrev)

定义 is.mass 以识别 mass 列,然后在这些列上定义 运行 na.locf。 (如果有前导 NA,第二行 na.locf 执行向后填充。如果您知道有 none 或者如果您想保留前导 NA,则可以省略该行。)类似地定义 is.density 指示密度列,然后在这些列上使用 na.fill。两条 na.locf 行的替代方法是单行 df[is.mass] <- na.approx(df[is.mass], method = "constant", rule = 2)

library(zoo)

df.orig <- df # optional in case you want to keep the input around

is.mass <- grepl("mass", names(df))
df[is.mass] <- na.locf(df[is.mass], na.rm = FALSE)
df[is.mass] <- na.locf(df[is.mass], na.rm = FALSE, fromLast = TRUE)

is.density <- grepl("density", names(df))
df[is.density] <- na.fill(df[is.density], 0)

给予:

> df

   A  B  C  D mass.mean mass.stdev mass.rsd density.mean density.stdev density.rsd
1 29 55 24 66         1          1      0.0            6             3         0.8
2 78 45  4 70         1          1      0.0            5             1         0.2
3 40 94 32 53         2          1      0.1            7             1         2.0
4 86 44 93 58         3          1      0.1            0             0         0.0
5 91 65 86 28         3          1      0.1            0             0         0.0
6  4 54 66 14         3          1      0.1            0             0         0.0
7 50  9 60 91         2          2      0.2            6             2         0.5
8 83 84 97 84         1          1      0.1            4             1         0.7

备注

我们用这个作为输入。这与问题中的相同,只是我们更正了密度中的拼写错误。我们还删除了 stringsAsFactors,因为数据完全是数字。

set.seed(123)
df <- data.frame("A" = sample(0:100,8), 
             "B" = sample(0:100,8),
             "C" = sample(0:100,8), 
             "D" = sample(0:100,8),
             "mass.mean" = c(1, NA, 2, 3, NA, NA, 2, 1), 
             "mass.stdev" = c(1, NA, 1, 1, NA, NA, 2, 1),
             "mass.rsd" = c(0, NA, 0.1, 0.1, NA, NA, 0.2, 0.1), 
             "density.mean" = c(6, 5, 7, NA, NA, NA, 6, 4), 
             "density.stdev" = c(3, 1, 1, NA, NA, NA, 2, 1),
             "density.rsd" = c(0.8,0.2, 2, NA, NA, NA, 0.5, 0.7))