Data.table vs dplyr: применить функцию, возвращающую изменение имен столбцов в группах

Я хочу применить функцию (ratefunc()) к сгруппированному фрейму данных, которая возвращает изменение имен столбцов в зависимости от результата:

library(data.table)
library(dplyr)

dt <- data.table:::data.table(
  group=c(rep("A", 3), rep("B", 3)), 
  x=c(1:3, 6:8), 
  y=c(4:6, 9:11)
)

ratefunc <- function(data, x_col = "x", y_col = "y") {
  res <- sum(data[[x_col]] + data[[y_col]])
  if (res < 25) {
    return(
      data.frame(a=rep("a", nrow(data)))
    )
  } else {
    return(
      data.frame(b=rep("b", nrow(data)))
    )
  }
}

dplyr возвращает желаемый результат

  • назначение нового столбца каждому возвращаемому имени столбца и
  • установка NA для наблюдения за другими группами
dt %>% 
  group_by(group) %>% 
  group_modify(
    ~ratefunc(data=.)
  )

Вывод консоли:

# A tibble: 6 × 3
# Groups:   group [2]
  group a     b    
  <chr> <chr> <chr>
1 A     a     NA   
2 A     a     NA   
3 A     a     NA   
4 B     NA    b    
5 B     NA    b    
6 B     NA    b    

data.table вместо этого объединяет результаты в один столбец, игнорируя столбцы с разными именами:

dt[
  ,
  ratefunc(
    data=.SD
  )
  ,
  by=group
]

Вывод консоли:

    group      a
   <char> <char>
1:      A      a
2:      A      a
3:      A      a
4:      B      b
5:      B      b
6:      B      b

Как я могу получить тот же результат, что и dplyr, при использовании data.table?

И как можно оценить подход к выбору столбцов фрейма данных по имени столбца в ratefunc() (т. е. x_col и y_col как строковые входные данные) в отличие от их прямого выбора (т. е. x_col и y_col как входные вектора/столбцы)?

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

Ответы 4

Вы можете попробовать лучше ratefunc.

> ratefunc <- function(data, x_col = "x", y_col = "y") {
+   res <- sum(data[[x_col]] + data[[y_col]])
+   o <- t(array(, c(2L, nrow(data)), list(c('a', 'b'), NULL)))
+   if (res < 25) {
+       o[, 'a'] <- 'a'
+   } else {
+       o[, 'b'] <- 'b'
+   }
+   o |> as.data.frame()
+ }
> 
> dt[
+   ,
+   ratefunc(
+     data=.SD
+   )
+   ,
+   by=group
+ ]
    group      a      b
   <char> <char> <char>
1:      A      a   <NA>
2:      A      a   <NA>
3:      A      a   <NA>
4:      B   <NA>      b
5:      B   <NA>      b
6:      B   <NA>      b

Я тоже об этом думал, но мне явно хотелось бы избежать ratefunc(), чтобы рассмотреть все возможные результаты столбца и получить больше NA, чем результатов.

Cevior 17.07.2024 10:10

@Cevior В ОП вы «хотите применить функцию (ratefunc())», а теперь хотите ее избежать.

jay.sf 17.07.2024 10:53

Я думаю, что это правильный путь, за исключением того, что я бы использовал список вместо массива (sapply(c("a", "b"), \(.) rep(NA_character_, n), simplify = FALSE) или аналогичный.

s_baldur 17.07.2024 11:00
Ответ принят как подходящий

Ваш ratefunc — это не то, что я бы создал. Я бы сделал что-то вроде этого. При необходимости это можно обернуть в функцию.

dt[, tmp := fifelse(sum(Reduce(`+`, .SD)) < 25, "a", "b"), 
   by = group, .SDcols = c("x", "y")]
dt[, ind := .I]
dcast(dt, group + ind ~ tmp, value.var = "tmp")
#Key: <group, ind>
#    group   ind      a      b
#   <char> <int> <char> <char>
#1:      A     1      a   <NA>
#2:      A     2      a   <NA>
#3:      A     3      a   <NA>
#4:      B     4   <NA>      b
#5:      B     5   <NA>      b
#6:      B     6   <NA>      b

Изменить адрес дополнительных требований из комментариев:

dt[, colnames := fifelse(sum(Reduce(`+`, .SD)) < 25, "col1", "col2"), 
   by = group, .SDcols = c("x", "y")]

dt[data.table(colnames = c("col1", "col2"),
              colvalues = c("a", "b")), 
   colvalues := i.colvalues, on = .(colnames)]

dt[, ind := .I]
dcast(dt, group + ind ~ colnames, value.var = "colvalues")
# Key: <group, ind>
#     group   ind   col1   col2
#    <char> <int> <char> <char>
# 1:      A     1      a   <NA>
# 2:      A     2      a   <NA>
# 3:      A     3      a   <NA>
# 4:      B     4   <NA>      b
# 5:      B     5   <NA>      b
# 6:      B     6   <NA>      b

Хорошо, интересно. Как бы вы обернули это в функцию? И будет ли эта функция адаптирована к data.table и не будет ли применяться при использовании dplyr?

Cevior 17.07.2024 10:25

Имена столбцов «a» и «b» явно не определены в функции dcast. Вместо этого они выводятся из уникальных значений в столбце «tmp» исходной таблицы данных, а функция dcast создает отдельные столбцы для каждого уникального значения, найденного в «tmp». Как бы вы динамически назначали новые имена столбцов независимо от значений столбцов?

Cevior 17.07.2024 10:48

См. редактирование. Да, я использую data.table для повышения эффективности, и этот подход адаптирован для data.table. Очевидно, что с dplyr это не будет работать.

Roland 17.07.2024 11:21

Хорошо, мне кажется, это компромисс между: эффективностью = адаптированностью к data.table и чистыми = модульными функциями. Возможно, добавление имени столбца в качестве отдельного столбца к выводу ratefunc() (как выходной файл длинного формата) и применение dcast() к нему может быть компромиссом.

Cevior 17.07.2024 11:36

Вот еще один подход:

library(data.table)

dt <- data.table:::data.table(
  group=c(rep("A", 3), rep("B", 3)), 
  x=c(1:3, 6:8), 
  y=c(4:6, 9:11)
)

ratefunc <- function(data, x_col = "x", y_col = "y") {
  DT <- copy(data) # avoid modifying dt
  DT[, c("a", "b") := .(
    fifelse(sum(.SD) < 25L, yes = "a", no = NA_character_),
    fifelse(sum(.SD) < 25L, yes = NA_character_, no = "b")
  ), .SDcols = c(x_col, y_col) , by = group][, c(x_col, y_col) := NULL]
}

resultDT <- ratefunc(dt, x_col = "x", y_col = "y")
resultDT[]

Альтернатива: применяется непосредственно к dt:

x_col <- "x"
y_col <- "y"

dt[, c("a", "b") := .(
  fifelse(sum(.SD) < 25L, yes = "a", no = NA_character_),
  fifelse(sum(.SD) < 25L, yes = NA_character_, no = "b")
), .SDcols = c(x_col, y_col) , by = group][, c(x_col, y_col) := NULL]

Тест (возможно, вам придется проверить, как он масштабируется с вашими данными):

Unit: milliseconds
               expr     min      lq    mean  median      uq     max neval
 B.ChristianKamgang  1.0797  1.0797  1.0797  1.0797  1.0797  1.0797     1
             Roland  4.5069  4.5069  4.5069  4.5069  4.5069  4.5069     1
             jay.sf 15.7749 15.7749 15.7749 15.7749 15.7749 15.7749     1
      ismirsehregal  1.5379  1.5379  1.5379  1.5379  1.5379  1.5379     1

library(data.table)
library(microbenchmark)

dt <- data.table:::data.table(
  group = c(rep("A", 3), rep("B", 3)),
  x = c(1:3, 6:8),
  y = c(4:6, 9:11)
)

dt1 <- copy(dt)
dt2 <- copy(dt)
dt3 <- copy(dt)
dt4 <- copy(dt)

ratefunc_jay.sf <- function(data, x_col = "x", y_col = "y") {
  res <- sum(data[[x_col]] + data[[y_col]])
  o <- t(array(, c(2L, nrow(data)), list(c('a', 'b'), NULL)))
  if (res < 25) {
    o[, 'a'] <- 'a'
  } else {
    o[, 'b'] <- 'b'
  }
  o |> as.data.frame()
}

microbenchmark(
  B.ChristianKamgang = {
    res <- dt4[, if (sum(.SD) > 25) 
      .(a = rep("a", .N), b = NA_character_) 
      else 
        .(a = NA_character_, b = rep("b", .N)), 
      by = group, 
      .SDcols = c("x", "y")]
  },
  Roland = {
    dt1[, tmp := fifelse(sum(Reduce(`+`, .SD)) < 25, "a", "b"), by = group, .SDcols = c("x", "y")]
    dt1[, ind := .I]
    dcast(dt1, group + ind ~ tmp, value.var = "tmp")
  },
  jay.sf = {
    dt2[, ratefunc_jay.sf(data = .SD), by = group]
  },
  ismirsehregal = {
    dt3[, c("a", "b") := .(
      fifelse(sum(.SD) < 25L, yes = "a", no = NA_character_),
      fifelse(sum(.SD) < 25L, yes = NA_character_, no = "b")
    ), .SDcols = c("x", "y") , by = group][, c("x", "y") := NULL]
  },
  times = 1L
)

Пожалуйста, не оценивайте результаты с помощью крошечных входных данных.

Roland 17.07.2024 11:22

Просто добавил заметку.

ismirsehregal 17.07.2024 11:23

Я не думаю, что создавать функцию для этой проблемы — хорошая идея, но если вы считаете обратное, вам нужно будет изменить ее (см. конец ответа):

Найдите способ решить свою проблему ниже:

Если вы хотите добавить новые столбцы в свой набор данных, используйте

dt[, a := if (sum(.SD) > 25L) "a", by=group, .SDcols=c("x", "y")][is.na(a), b := "b"]

или функцию ниже (более динамичную/гибкую):

ratefunc = function(dt, x_col = "x", y_col = "y", nm1, nm2, val1, val2) {
    dt[, (nm1) := if (sum(x_col, y_col) > 25L) val1, by=group, env=list(x_col=x_col, y_col=y_col, val1=I(val1))]
    dt[is.na(nm1), (nm2) := val2, env=list(nm1=nm1, val2=I(val2))][]  
}

ratefunc(dt=dt, nm1 = "a", nm2 = "b", val1 = "a", val2 = "b")

    group     x     y      a      b
   <char> <int> <int> <char> <char>
1:      A     1     4   <NA>      b
2:      A     2     5   <NA>      b
3:      A     3     6   <NA>      b
4:      B     6     9      a   <NA>
5:      B     7    10      a   <NA>
6:      B     8    11      a   <NA>

В противном случае используйте:

dt[, if (sum(.SD) > 25) 
     .(a = rep("a", .N), b = NA_character_) 
     else 
     .(a = NA_character_, b = rep("b", .N)), 
   by = group, 
   .SDcols = c("x", "y")]

    group      a      b
   <char> <char> <char>
1:      A   <NA>      b
2:      A   <NA>      b
3:      A   <NA>      b
4:      B      a   <NA>
5:      B      a   <NA>
6:      B      a   <NA>

Найдите модифицированную версию вашей функции ниже, если вы предпочитаете использовать ее вместо этого.

ratefunc <- function(data, x_col = "x", y_col = "y") {
  res <- sum(data[[x_col]] + data[[y_col]])
    if (res < 25) {
    return(
      list(a=rep("a", nrow(data)), b=NA_character_)     # modified line
    )
  } else {
    return(
      list(a = NA_character_, b=rep("b", nrow(data)))   # modified line
    )
  }
}

dt[, ratefunc(data=.SD), by=group]

См. мой комментарий к ответу jay.sf, я явно хотел бы избежать ratefunc(), чтобы рассмотреть все возможные результаты столбца и получить больше NA, чем результата.

Cevior 17.07.2024 11:58

Я не понимаю, чего вы хотите, даже прочитав ответ jay.sf. Что вы подразумеваете под всеми возможными результатами столбца и получением большего количества NA, чем результата? Кроме того, зачем вы создали ratefunc(), если хотите этого избежать? Не могли бы вы изменить свое сообщение, чтобы оно лучше отражало вашу проблему (включив в комментарий то, что вы имеете в виду)?

B. Christian Kamgang 17.07.2024 12:05

Это быстрый и читаемый подход! (вверх)

ismirsehregal 17.07.2024 12:25

Имена столбцов, возможные в минимальном примере, не должны быть исключительными, а скорее динамическими, что четко указано в вопросе. Вы, как и jay.sf, неявно делаете это предположение, поскольку ваш код не является динамическим для имен столбцов, отличных от «a» и «b». ЕСЛИ эта модульная функция ratefunc() не лучший способ работы с data.table, как указал Роланд, ТОГДА стоит подумать об интеграции ratefunc() в ситнакс data.table, чтобы создать одношаговый подход (вместо двухэтапного подхода). пошаговый подход с жестко запрограммированным выводом имени столбца).

Cevior 17.07.2024 13:53

Хотели бы вы, чтобы значение столбца (а не имя столбца) также было динамическим (то есть «a» и «b», используемые в функции rep)?

B. Christian Kamgang 17.07.2024 14:34

Да, я сделал пример максимально минимальным.

Cevior 17.07.2024 14:53

Я внес некоторые изменения, чтобы сделать имена и значения столбцов динамическими. Дайте мне знать, если это решит вашу проблему.

B. Christian Kamgang 17.07.2024 15:31

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