簡體   English   中英

創建幾個新列,按名稱組合舊列

[英]Create several new columns combining old ones by name

假設我有一個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”列。

您可以按names(df)的第一個字母按列拆分數據( 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 Rbase 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)
))

使用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等)的新列paste0(names(.)[1],".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

您可以執行以下操作:

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

下面提出了一個更好的解決方案。

如果您將它變成長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)
)

這是另一個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

請注意 ,您必須確保列在原始數據集中的順序正確。

使用數據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)

我建議進行以下轉換:

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)

只需做:

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

請注意:

  • Base解決方案-無需第三方軟件包。
  • 按列順序指定時一般。

如果要依賴名稱,可以使用簡單的for循環:

# name_vec <- LETTERS[1:3]
name_vec <- names(df1)[grepl("^[^.]+$",names(df1))]


for( name_el in name_vec) {
    df1[paste0(name_el, ".cv")] <- df1[[paste0(name_el, ".sd")]]/df1[[name_el]]
}
> 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")
    } 
)

規范和簡約的方法是從寬變長,重新計算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

此方法也與均值/標准差列的順序無關。

按OP編輯:

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)

這樣,結果在同一數據幀中,而沒有“行”列。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM