разделить столбцы, используя шаблон в именах столбцов - PullRequest
2 голосов
/ 22 января 2020

Предполагая, что у меня есть следующая упрощенная таблица, которая имеет динамические c столбцы a_x (где x - это индекс, например, 0, 1, 2, 3, 4 ...) и b_x соответственно. Количество a столбцов всегда равно количеству b столбцов, но общее количество столбцов может быть динамическим c (не всегда 3 a и 3 b). Чтобы было понятнее, в следующем примере показана структура моих данных:

> d <- read.table(text = "10 20 25 0.3 0.23 0.34 
                          40 20 30 0.25 0.4 0.45")
> names(d) <- c("a_0", "a_1", "a_2", "b_0", "b_1", "b_2")
> d
   a_0 a_1 a_2  b_0  b_1  b_2
1  10  20  25   0.30 0.23 0.34
2  40  20  30   0.25 0.40 0.45

Я хотел бы разделить столбцы a на соответствующие столбцы b и сохранить результаты в новых столбцах c. Чтобы сделать деления, я использую функцию transform () (с жестко закодированными именами столбцов) следующим образом:

transform(d, c_0 = as.numeric(as.character(a_0)) / as.numeric(as.character(b_0)))

Как я могу сделать этот шаг автоматически, используя (вероятно) шаблон в именах столбцов, учитывая тот факт, количество столбцов моих входных данных не всегда одинаково.

Любая помощь будет оценена

Ответы [ 6 ]

4 голосов
/ 22 января 2020

Вот несколько подходов. (1) и (1а) кажутся лучшими, но другие показывают разные подходы. Они имеют те же имена столбцов и порядок, что и в вопросе, за исключением (1a) и (2), но их можно легко исправить, если это будет проблемой. Пакеты не используются, кроме (4a).

1) transform

ix <- grep("a", names(d))
cbind(d, setNames(d[ix] / d[-ix], sub("a", "c", names(d)[ix])))
##   a_0 a_1 a_2  b_0  b_1  b_2       c_0      c_1      c_2
## 1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
## 2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667

1a) Это вариант (1) ;

transform(d, c = setNames(d[ix], ix-1) / d[-ix])  # ix is from above
##   a_0 a_1 a_2  b_0  b_1  b_2       c.0      c.1      c.2
## 1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
## 2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667

2) изменить форму Преобразовать в длинную форму, выполнить деление и преобразовать обратно в широкую форму.

varying <- split(names(d), sub("_.*", "", names(d)))
long <- reshape(d, dir = "long", varying = varying, v.names = names(varying))
reshape(transform(long, c = a / b), dir = "wide", idvar = "id")[-1]
##     a.1  b.1       c.1 a.2  b.2      c.2 a.3  b.3      c.3
## 1.1  10 0.30  33.33333  20 0.23 86.95652  25 0.34 73.52941
## 2.1  40 0.25 160.00000  20 0.40 50.00000  30 0.45 66.66667

3) применить Мы можем преобразовать в трехмерный массив, а затем использовать apply.

nr <- nrow(d)
nc <- ncol(d)
cc <- apply(array(as.matrix(d), c(nr, nc / 2, 2)), 1:2, function(x) x[1] / x[2])
colnames(cc) <- paste("c", seq(0, length = ncol(cc)), sep = "_")
cbind(d, cc)
##   a_0 a_1 a_2  b_0  b_1  b_2       c_0      c_1      c_2
## 1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
## 2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667

4) diff Транспонировать лог d, брать дифференциалы и реверсировать транспонирование беру эксп транспонировать. Затем свяжите это с d. Это решение предполагает, что все записи строго положительны (что имеет место в вопросе), так что мы можем взять журналы.

nc <- ncol(d)
cc <- t(exp(-diff(t(log(d)), nc/2)))
colnames(cc) <- paste("c", seq(0, length = ncol(cc)), sep = "_")
cbind(d, cc)
##   a_0 a_1 a_2  b_0  b_1  b_2       c_0      c_1      c_2
## 1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
## 2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667

(4a) diff.zoo поддерживает геометрию c diff, который выполняет деление, а не вычитание. (В текущей версии zoo diff.zoo требует, чтобы элементы ввода были строго положительными, но это ограничение снято в версии разработки zoo.)

library(zoo)

nc <- ncol(d)
cc <- 1 / t(diff(zoo(t(d)), nc/2, arith = FALSE))
colnames(cc) <- paste("c", seq(0, length = ncol(cc)), sep = "_")
cbind(d, cc)
##     a_0 a_1 a_2  b_0  b_1  b_2       c_0      c_1      c_2
## x.1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
## x.2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667
2 голосов
/ 22 января 2020

Мы можем удалить все после подчеркивания из имен, разделить их и разделить по одному, т.е.

Reduce(`/`, split.default(d, gsub('_.*', '', names(d))))
#        a_0      a_1      a_2
#1  33.33333 86.95652 73.52941
#2 160.00000 50.00000 66.66667
1 голос
/ 22 января 2020

Один вариант, включающий dplyr, может быть:

rename_all(select(d, starts_with("a"))/select(d, -starts_with("a")), 
           ~ paste("c", 1:(ncol(d)/2), sep = "_"))

        c_1      c_2      c_3
1  33.33333 86.95652 73.52941
2 160.00000 50.00000 66.66667
1 голос
/ 22 января 2020

Вы можете использовать grep, чтобы найти столбцы "a" и "b", и добавить результат в виде матрицы с хорошим setNames в вашем transform.

transform(d, ind=setNames(d[, grep("a", names(d))] / d[, grep("b", names(d))], 
                          gsub(".*(\\D)", "", grep("a", names(d), value=T))))
#   a_0 a_1 a_2  b_0  b_1  b_2     ind.0    ind.1    ind.2
# 1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
# 2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667
0 голосов
/ 22 января 2020

tidyr на самом деле имеет действительно замечательную новую функцию для этого. Она называется pivot_longer, которая является более сложной версией функции gather

d$id <- 1:nrow(d)
d.new <- d %>% pivot_longer(a_0:b_2, #what to pivot
               names_to = c(".value", "index"), #how names will change
               names_pattern = "(.)_(.)") #where to match names_to in the column names
d.new
# A tibble: 6 x 4
     id index     a     b
  <int> <chr> <int> <dbl>
1     1 0        10  0.3 
2     1 1        20  0.23
3     1 2        25  0.34
4     2 0        40  0.25
5     2 1        20  0.4 
6     2 2        30  0.45

, отсюда вы просто не можете делать то, что хотите

d.new <- d.new %>%
    mutate(c = a/b)

Там также является сестринской функцией с именем pivot_wider, которая также может преобразовывать значения обратно в то, чем они были.

d <- d.new %>%
    pivot_wider(everything(), names_from = c(index), values_from = c(a,b,c))
d
# A tibble: 2 x 10
     id   a_0   a_1   a_2   b_0   b_1   b_2   c_0   c_1   c_2
  <int> <int> <int> <int> <dbl> <dbl> <dbl> <dbl> <dbl> <dbl>
1     1    10    20    25  0.3   0.23  0.34  33.3  87.0  73.5
2     2    40    20    30  0.25  0.4   0.45 160    50    66.7

Столбец id был добавлен так, что pivot_wider не запутывается в том, как отделить значения строки. Обе эти функции используют tidyselection, поэтому, если вы не знали, сколько индексов было в столбце, вместо того, чтобы сказать pivot_longer(a_0:b_2,, вы могли бы сказать pivot_longer(-id, и все функции, кроме id, будут использоваться в функции pivot_longer.

0 голосов
/ 22 января 2020

Вы можете удалить a или b из имен с помощью sub, чтобы получить все индексы и столбцы, используя paste0. нет необходимости сортировать столбцы.

x <- substring(grep("^a_\\d+$", names(d), value = TRUE), 2)
cbind(d, setNames(d[paste0("a",x)] / d[paste0("b",x)], paste0("c",x)))
#  a_0 a_1 a_2  b_0  b_1  b_2       c_0      c_1      c_2
#1  10  20  25 0.30 0.23 0.34  33.33333 86.95652 73.52941
#2  40  20  30 0.25 0.40 0.45 160.00000 50.00000 66.66667
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...