创建几个新列,按名称组合旧列

时间:2018-10-09 11:58:23

标签: r tidyverse

假设我有一个data.frame或tibble。该对象有几列。有些列是(ABC)是平均值,而其他列是标准差(A.sdB.sdC.sd

df <- 
  data.frame(
    A=c(1,2,3),
    A.sd=c(0.3, 0.2, 0.1),
    B=c(20,2,34),
    B.sd=c(2.1, 5.2, 5.1),
    C=c(14,26,13),
    C.sd=c(1.3, 0.7, 4.5)
  )

现在,我要计算变化系数(sd / mean)(这将是df$A.cv = df$A.sd/df$A,依此类推)。我可以一一做到。但我想知道是否tidyverse提供了一种更自动的方式来执行此操作。将“平均值”列与“ sd”列匹配的某种方法,以计算“ cv”列。

10 个答案:

答案 0 :(得分:1)

您可以按split.default的第一个字母按列(names(df))拆分数据,然后使用imap生成cv列。

library(tidyverse)
split.default(df, f = substr(names(df), 1, 1)) %>% 
  imap(.x = ., ~ mutate(., cv = .x[, paste0(.y, ".sd")] / .x[, .y])) %>% 
  imap(., ~ set_names(., nm = paste0(.y, c("", ".sd", ".cv")))) %>% # rename the columns
  bind_cols()
#  A A.sd       A.cv  B B.sd  B.cv  C C.sd       C.cv
#1 1  0.3 0.30000000 20  2.1 0.105 14  1.3 0.09285714
#2 2  0.2 0.10000000  2  5.2 2.600 26  0.7 0.02692308
#3 3  0.1 0.03333333 34  5.1 0.150 13  4.5 0.34615385

imap在这里很方便,因为它使您可以轻松地遍历列表并遍历该列表的名称(代码中的.y)。


这里需要第二个imap调用,因为这会产生错误

split.default(df, f = substr(names(df), 1, 1)) %>%
 imap(.x = ., ~ mutate(., paste0(.y, ".cv") = .x[, paste0(.y, ".sd")] / .x[, .y]))

相同的想法,但在base R

lst <- split.default(df, f = substr(names(df), 1, 1))
Reduce(cbind, Map(
  function(x, y)
    `[<-`(x, paste0(y, ".cv"), value = x[, paste0(y, ".sd")] / x[, y]),
  x = lst,
  y = names(lst)
))

答案 1 :(得分:1)

使用tidyversesplit.default

df %>% 
  split.default(substr(names(.),1,1)) %>%
  map_dfc(~mutate(., !!paste0(names(.)[1],".cv") := .[[2]]/.[[1]]))
#   A A.sd       A.cv  B B.sd  B.cv  C C.sd       C.cv
# 1 1  0.3 0.30000000 20  2.1 0.105 14  1.3 0.09285714
# 2 2  0.2 0.10000000  2  5.2 2.600 26  0.7 0.02692308
# 3 3  0.1 0.03333333 34  5.1 0.150 13  4.5 0.34615385
  • 第一行根据第一个字符分为3个数据帧。
  • 第二行为每个数据帧定义了一个名为paste0(names(.)[1],".cv")A.cv等)的新列,并将所有内容绑定在一起。

在基数R中:

df_list <- unname(split.default(df,substr(names(df),1,1)))
add_cv  <- function(x) `[[<-`(x, paste0(names(x)[1], ".cv"), value = x[[2]] / x[[1]])
do.call(cbind, lapply(df_list, add_cv))
#   A A.sd       A.cv  B B.sd  B.cv  C C.sd       C.cv
# 1 1  0.3 0.30000000 20  2.1 0.105 14  1.3 0.09285714
# 2 2  0.2 0.10000000  2  5.2 2.600 26  0.7 0.02692308
# 3 3  0.1 0.03333333 34  5.1 0.150 13  4.5 0.34615385

R基地再次分裂:

df_list <- split.default(df, endsWith(names(df),".sd"))
cbind(df, setNames(df_list[[2]] / df_list[[1]], paste0(names(df_list[[1]]), ".cv")))
#   A A.sd  B B.sd  C C.sd       A.cv  B.cv       C.cv
# 1 1  0.3 20  2.1 14  1.3 0.30000000 0.105 0.09285714
# 2 2  0.2  2  5.2 26  0.7 0.10000000 2.600 0.02692308
# 3 3  0.1 34  5.1 13  4.5 0.03333333 0.150 0.34615385

答案 2 :(得分:0)

您可以执行以下操作:

library(tidyverse)
df  %<>%  mutate(A.cv=A.sd/A,
                B.cv=B.sd/B,
                C.cv=C.sd/C)

下面提出了一个更好的解决方案。

答案 3 :(得分:0)

如果将其变成长DF,类似这样的事情相对容易:

library(tidyverse)

df <- data.frame(
groups = rep(c("A", "B", "C"), each = 3),
means = c(1, 2, 3, 20, 2, 34, 14, 26, 13),
sd = c(0.3, 0.2, 0.1, 2.1, 5.2, 5.1, 1.3, 0.7, 4.5)
)

df <- df %>% mutate(
       cv = (sd / means)
)

答案 4 :(得分:0)

这是另一种tidyverse版本:

df <- 
  data.frame(
    A=c(1,2,3),
    A.sd=c(0.3, 0.2, 0.1),
    B=c(20,2,34),
    B.sd=c(2.1, 5.2, 5.1),
    C=c(14,26,13),
    C.sd=c(1.3, 0.7, 4.5)
  )

library(tidyverse)

{df %>% select(matches("sd")) / df %>% select(-matches("sd"))} %>%
  setNames(gsub("sd", "cv", names(.))) %>%
  bind_cols(df, .)

#   A A.sd  B B.sd  C C.sd       A.cv  B.cv       C.cv
# 1 1  0.3 20  2.1 14  1.3 0.30000000 0.105 0.09285714
# 2 2  0.2  2  5.2 26  0.7 0.10000000 2.600 0.02692308
# 3 3  0.1 34  5.1 13  4.5 0.03333333 0.150 0.34615385

注意,您必须确保列在原始数据集中的顺序正确。

答案 5 :(得分:0)

使用数据df,您可以使用dplyr函数ends_with()将数据集一分为二,转换为long并再次绑定:

library(tidyverse)

df <-
  data.frame(
    A=c(1,2,3),
    A.sd=c(0.3, 0.2, 0.1),
    B=c(20,2,34),
    B.sd=c(2.1, 5.2, 5.1),
    C=c(14,26,13),
    C.sd=c(1.3, 0.7, 4.5)
  )


sds <- select(df, ends_with(".sd")) %>%
  gather() %>%
  rename(sd = value) %>%
  select(sd)

means <- select(df, -ends_with(".sd")) %>%
  gather() %>%
  rename(mean = value)

df_n <- bind_cols(means, sds)

df_n <- mutate(df_n, cv = sd/mean)

答案 6 :(得分:0)

我建议进行以下转换:

df %>%
    # Adding counter
    mutate(n = 1:n()) %>% 
    # Converting to long format
    gather("key", "value", -n) %>% 
    # Adding variable that distinguishes SD and mean
    mutate(type = ifelse(grepl("\\.sd$", key), "SD", "mean"),
           item = sub("(\\w).*", "\\1", key), # A, B, or C
           case = paste(item, n)) %>% # e.g., A 1, B 2, etc.
    select(n, value, type, case) %>% 
    # Conversion back to wide format
    spread("type", "value") %>% 
    # Calculating COV
    mutate(COV = mean / SD)

答案 7 :(得分:0)

只需:

for (ThresholdParams p : threshold.getThresholdParams()) {
    if (p !=null) {
            thresholdParamsRepository.save(p);
        }
    }

请注意:

  • IND <- rep(seq(1:(ncol(df1)/2)),each=2) df1[paste0(names(df1)[!duplicated(IND,F)], ".cv")] <- lapply(split(as.data.frame(t(df1)), IND), function(x)c(t(x)[,2]/t(x)[,1])) # A A.sd B B.sd C C.sd A.cv B.cv C.cv #1 1 0.3 20 2.1 14 1.3 0.30000000 0.105 0.09285714 #2 2 0.2 2 5.2 26 0.7 0.10000000 2.600 0.02692308 #3 3 0.1 34 5.1 13 4.5 0.03333333 0.150 0.34615385 解决方案-无需第三方软件包。
  • 按列顺序给出时一般。

如果要依赖名称,可以使用简单的for循环:

Base

答案 8 :(得分:0)

> cv
        A.cv  B.cv       C.cv
1 0.30000000 0.105 0.09285714
2 0.10000000 2.600 0.02692308
3 0.03333333 0.150 0.34615385

代码

显然超级hacky,还有很大的优化空间,但很可能实现了您的目标。

cv <- data.frame()
counter <- 0

for (i in 1:ncol(df))(
    if (grepl("sd$", colnames(df)[i]) == TRUE){
        counter <- counter + 1
        for (j in 1:nrow(df))(
            cv[j, counter] <- df[j, i]/df[j, i-1]
        )
        names(cv)[counter] <- paste0(colnames(df)[i-1],".cv")
    } 
)

答案 9 :(得分:0)

规范和简约的方法是从宽变长,重新计算CV,然后从长变宽(如有必要)。

library(tidyverse)
df %>%
    rowid_to_column("row") %>%
    gather(key, value, -row) %>%
    mutate(key = str_replace(key, "^([A-Z])$", "\\1.mean")) %>%
    separate(key, c("var", "col")) %>%
    spread(col, value) %>%
    transmute(row, var = paste0(var, ".cv"), cv = sd / mean) %>%
    spread(var, cv)
#  row       A.cv  B.cv       C.cv
#1   1 0.30000000 0.105 0.09285714
#2   2 0.10000000 2.600 0.02692308
#3   3 0.03333333 0.150 0.34615385

这种方法也与均值/标准差列的顺序无关。

按操作编辑:

df %>%
    rowid_to_column("row") %>%
    gather(key, value, -row) %>%
    mutate(key = str_replace(key, "^([A-Z])$", "\\1.mean")) %>%
    separate(key, c("var", "col")) %>%
    spread(col, value) %>%
    transmute(row, var = paste0(var, ".cv"), cv = sd / mean) %>%
    spread(var, cv) %>% 
    bind_cols(df, .) %>% 
    select(-row)

这样,结果位于同一数据帧中,而没有“行”列。