Как умножить каждый столбец во фрейме данных на другое значение для каждого столбца

Рассмотрим следующий фрейм данных

   x y z
 1 0 0 0
 2 1 0 0
 3 0 1 0
 4 1 1 0
 5 0 0 1
 6 1 0 1
 7 0 1 1
 8 1 1 1
 -------
 x 4 2 1  <--- vector to multiply by 
 

Я хотел бы умножить каждый столбец на отдельное значение, например c (4,2,1). Предоставление:

   x y z
 1 0 0 0
 2 4 0 0
 3 0 2 0
 4 4 2 0
 5 0 0 1
 6 4 0 1
 7 0 2 1
 8 4 2 1

Код:

pw2 <- c(4, 2, 1)
s01  <- seq_len(2) - 1
df  <- expand.grid(x=s01, y=s01, z=s01)
df

for (d in seq_len(3)) df[,d] <- df[,d] * pw2[d]
df

Вопрос: Найдите векторизованное решение без цикла for (в базе R).

Примечание: что вопрос Умножить столбцы во фрейме данных на вектор неоднозначен, поскольку включает в себя:

  • умножьте каждую строку в столбце фрейма данных на другое значение.
  • умножьте каждый столбец во фрейме данных на другое значение.

Оба запроса легко решаются с помощью цикла for. Здесь явно запрашивается векторизованное решение.

Вероятно, лучший из многих ответов на несколько вопросов именно об этой проблеме: stackoverflow.com/a/65327572/9463489

jblood94 12.04.2023 13:42
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
13
1
963
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Используйте sweep, чтобы применить функцию к полям фрейма данных:

sweep(df, 2, pw2, `*`)

или с col:

df * pw2[col(df)]

выход

  x y z
1 0 0 0
2 4 0 0
3 0 2 0
4 4 2 0
5 0 0 1
6 4 0 1
7 0 2 1
8 4 2 1

Для больших кадров данных проверьте collapse::TRA, который в 10 раз быстрее, чем любые другие ответы (см. тест):

collapse::TRA(df, pw2, "*")

Ориентир:

bench::mark(sweep = sweep(df, 2, pw2, `*`),
            col = df * pw2[col(df)],
            '%*%' = setNames(
              as.data.frame(as.matrix(df) %*% diag(pw2)), 
              names(df)
            ), 
            TRA = collapse::TRA(df, pw2, "*"), 
            mapply = data.frame(mapply(FUN = `*`, df, pw2)),
            apply = t(apply(df, 1, \(x) x*pw2)), 
            t = t(t(df)*pw2), check = FALSE,
            )

# A tibble: 7 × 13
  expression      min  median itr/s…¹ mem_al…² gc/se…³ n_itr  n_gc total…⁴
  <bch:expr> <bch:tm> <bch:t>   <dbl> <bch:by>   <dbl> <int> <dbl> <bch:t>
1 sweep       346.7µs 382.1µs   2427.   1.23KB   10.6   1141     5 470.2ms
2 col         303.1µs 330.4µs   2760.     784B    8.45  1307     4 473.5ms
3 %*%          72.8µs  77.9µs  11861.     480B   10.6   5599     5 472.1ms
4 TRA             5µs   5.5µs 167050.       0B   16.7   9999     1  59.9ms
5 mapply      117.6µs 127.9µs   7309.     480B   10.6   3442     5 470.9ms
6 apply       107.8µs 117.9µs   7887.   6.49KB   12.9   3658     6 463.8ms
7 t            55.3µs  59.7µs  15238.     720B    8.13  5620     3 368.8ms

Это быстрее, чем t(t(df)*pw2)?

Onyambu 10.04.2023 18:40

Да, хотя ваш ответ является хорошим кандидатом

Maël 10.04.2023 18:44

Преобразуйте df и pw2 в матрицы, используйте оператор умножения матриц %*%, а затем преобразуйте обратно в фрейм данных. Это удалит имена столбцов, поэтому оберните их setNames(), чтобы сохранить их.

setNames(
  as.data.frame(as.matrix(df) %*% diag(pw2)), 
  names(df)
)
  x y z
1 0 0 0
2 4 0 0
3 0 2 0
4 4 2 0
5 0 0 1
6 4 0 1
7 0 2 1
8 4 2 1

используя mapply():

mapply(FUN = `*`, df, pw2)

     x y z
[1,] 0 0 0
[2,] 4 0 0
[3,] 0 2 0
[4,] 4 2 0
[5,] 0 0 1
[6,] 4 0 1
[7,] 0 2 1
[8,] 4 2 1

и как фрейм данных:

data.frame(mapply(FUN = `*`, df, pw2))
  x y z
1 0 0 0
2 4 0 0
3 0 2 0
4 4 2 0
5 0 0 1
6 4 0 1
7 0 2 1
8 4 2 1

Другой вариант использования apply с транспонированием:

pw2 <- c(4, 2, 1)
t(apply(df, 1, \(x) x*pw2))
#>   x y z
#> 1 0 0 0
#> 2 4 0 0
#> 3 0 2 0
#> 4 4 2 0
#> 5 0 0 1
#> 6 4 0 1
#> 7 0 2 1
#> 8 4 2 1

Created on 2023-04-10 with reprex v2.0.2

Вот еще один вариант, когда вы превращаете вектор в матрицу тех же размеров, что и ваш фрейм данных, а затем просто умножаете два:

t(replicate(nrow(df), pw2)) * df

Выход

  x y z
1 0 0 0
2 4 0 0
3 0 2 0
4 4 2 0
5 0 0 1
6 4 0 1
7 0 2 1
8 4 2 1

Существующий подход mapply среди всех ответов выглядит великолепно, но я считаю, что мы можем добиться большей эффективности, если вместо этого будем использовать Map + list2DF (особенно если вы предпочитаете оставаться с базой R)


Ниже приведен ориентир для вариантов mapply и Map.

microbenchmark(
  "mapply1" = data.frame(mapply(FUN = `*`, df, pw2)),
  "mapply2" = as.data.frame(mapply(FUN = `*`, df, pw2)),
  "Map1" = list2DF(Map(`*`, df, pw2)),
  "Map2" = list2DF(Map(`*`, df, as.list(pw2)))
)

дает

Unit: microseconds
    expr  min    lq    mean median     uq   max neval
 mapply1 74.6 78.60 112.163  97.05 140.50 342.6   100
 mapply2 34.6 38.20  55.513  42.70  67.40 313.5   100
    Map1 23.8 25.25  33.728  27.60  41.30 113.8   100
    Map2 25.9 28.75  40.866  32.95  47.65 238.6   100

Кроме того, пусть Map подход присоединится к группе бенчмаркинга, предоставленной @Maël, например,

bc <- bench::mark(
  sweep = sweep(df, 2, pw2, `*`),
  col = df * pw2[col(df)],
  "%*%" = setNames(
    as.data.frame(as.matrix(df) %*% diag(pw2)),
    names(df)
  ),
  TRA = collapse::TRA(df, pw2, "*"),
  mapply1 = data.frame(mapply(FUN = `*`, df, pw2)),
  mapply2 = as.data.frame(mapply(FUN = `*`, df, pw2)),
  Map1 = list2DF(Map(`*`, df, pw2)),
  Map2 = list2DF(Map(`*`, df, as.list(pw2))),
  apply = t(apply(df, 1, \(x) x * pw2)),
  t = t(t(df) * pw2),
  check = FALSE,
)

мы увидим, что Map находится на втором месте по эффективности

# A tibble: 10 × 13
   expression      min   median `itr/sec` mem_alloc `gc/sec` n_itr  n_gc
   <bch:expr> <bch:tm> <bch:tm>     <dbl> <bch:byt>    <dbl> <int> <dbl>
 1 sweep       201.7µs  249.2µs     3526.  101.24KB     12.6  1680     6
 2 col         174.9µs  225.6µs     3637.    9.02KB     10.4  1748     5
 3 %*%          45.4µs   52.9µs    17026.   36.95KB     12.5  8158     6
 4 TRA           3.4µs    3.8µs   226089.  905.09KB     22.6  9999     1
 5 mapply1      71.6µs   78.4µs    11958.      480B     14.7  5681     7
 6 mapply2      33.1µs   37.4µs    25339.      480B     17.7  9993     7
 7 Map1         22.5µs   26.1µs    35649.        0B     17.8  9995     5
 8 Map2         25.3µs   29.4µs    31785.        0B     19.1  9994     6
 9 apply        70.2µs   80.7µs    11684.   11.91KB     14.7  5562     7
10 t            34.8µs   40.2µs    23608.    3.77KB     14.2  9994     6
# ℹ 5 more variables: total_time <bch:tm>, result <list>, memory <list>,
#   time <list>, gc <list>

и autoplot(bc) шоу

Другие вопросы по теме