Альтернатива mutate_if в data.table - PullRequest
1 голос
/ 11 июля 2019

Я активно использую Tidyverse, но для некоторых проектов мне нужна скорость data.table. Пока я понимаю большую часть синтаксиса DT, но я хочу отбросить неиспользуемые уровни в data.table без использования mutate_if.

С dplyr я могу использовать mutate_if(dataframe, is.factor, droplevels) и все. Однако я не могу обойтись с data.table.

Я пытался применить этот ответ , используя dataframe[, (.SD) := droplevels(.SD), .SDcols = sapply(dataframe, is.factor)]

Выдает следующую ошибку: Error in [. Data.table (DT_, ,: = ((.SD), droplevels(.SD)), .SDcols = sapply(DT_, : LHS of := isn't column names ('character') or positions ('integer' or 'numeric').

Я ожидаю, что получу тот же результат, что и в mutate_if, без использования Tidyverse.

UPDATE

Я принял G. Гротендик ответил , потому что код был больше, чем я ожидал.

Пример, который он использовал:

library(data.table)
DT <- data.table(a = 1:5, 
                 b = factor(1:5, levels = 1:10), 
                 c = factor(6:10, levels = 1:10))

Данные, которые я использовал для этого примера, были следующие:

set.seed(42)
DT1 = data.table(
  A = LETTERS[1:10],
  B = c(1:10),
  C = factor(sample(LETTERS, 10), levels = LETTERS),
  D = factor(sample(LETTERS, 10), levels = LETTERS)
)

Интересующие столбцы:

> DT1[, C]
 [1] Q E A J D R Z O G V
Levels: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
> DT1[, D]
 [1] Y E N T R O C I D Z
Levels: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z

И результаты

# with base
DT1 = droplevels(DT1)

# or by reference
DT1[, (names(DT1)) := droplevels(.SD)]

Со следующим выводом:

> DT1[, C]
 [1] Q E A J D R Z O G V
Levels: A D E G J O Q R V Z
> DT1[, D]
 [1] Y E N T R O C I D Z
Levels: C D E I N O R T Y Z

Спасибо всем за ваши ответы, это было быстро!

Ответы [ 5 ]

5 голосов
/ 11 июля 2019

Использование данных в примечании в конце

DT[, (names(DT)) := droplevels(.SD)]

или

DT <- droplevels(DT)

Проверка:

levels(DT$b)
## [1] "1" "2" "3" "4" "5"

levels(DT$c)
## [1] "6"  "7"  "8"  "9"  "10"

Если droplevels в вопросепросто пример, и реальная функция, которую вы используете, не имеет метода data.frame, затем используйте код, соответствующий этому:

wx <- which(sapply(DT, is.factor))
DT[, (wx) := lapply(.SD, droplevels), .SDcols = wx]

Примечание

library(data.table)
DT <- data.table(a = 1:5, 
                 b = factor(1:5, levels = 1:10), 
                 c = factor(6:10, levels = 1:10))

Обновление

Упрощенная.

4 голосов
/ 11 июля 2019

Не data.table решение, но это можно сделать аккуратно с помощью базовых R rapply:

## data
data("iris")
## add dummy level
levels(iris$Species) <- c(levels(iris$Species), "dummy")
str(iris)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 4 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

iris2 <- rapply(iris, f = droplevels, classes = "factor", how = "replace")
str(iris2)
#> 'data.frame':    150 obs. of  5 variables:
#>  $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
#>  $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
#>  $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
#>  $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
#>  $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
3 голосов
/ 11 июля 2019

Другой вариант с использованием set()

Входные данные

library(data.table)
DT <- as.data.table(iris)
DT[, Species := as.factor(Species)]
DT <- DT[Species == "setosa"]

DT[, levels(Species)]
#[1] "setosa"     "versicolor" "virginica"

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

cols <- DT[, names(Filter(is.factor, .SD))]
for(j in cols) {
  set(DT, j = j, value = droplevels(DT[[j]]))
}
# could also be written as a one-liner - thanks to @MattSummersgill
# for(j in cols) set(DT, j = j, value = droplevels(DT[[j]]))

Предоставление

DT[, levels(Species)]
#[1] "setosa"
2 голосов
/ 11 июля 2019

Чтобы добавить мой комментарий, вы можете попробовать table.express, хотя примеры следует обновить, поскольку их можно упростить.Вот один пример, эквивалентный mutate_if:

library(data.table)
library(table.express)

data("iris")

DT <- as.data.table(iris)

DT %>%
  start_expr %>%
  mutate(Species = as.factor(Species)) %>%
  mutate_sd(is.factor(.COL), droplevels) %>%
  end_expr

Проверьте всю виньетку, хотя некоторые глаголы стремятся, а некоторые ленивы.

1 голос
/ 11 июля 2019

Как насчет этого?

x <- data.table(
  x=sample(letters[1:5],10,rep=T), 
  y=factor(sample(letters[1:5],10,rep=T), levels=letters),
  w=factor(sample(letters[1:5],10,rep=T), levels=letters)
  )
factors <- colnames(x)[sapply(x, is.factor)]
lapply(factors, function(z) x[, eval(z):=droplevels(get(z))])
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...