Заявление о регистре, эквивалентное R


87

У меня есть переменная в фрейме данных, где одно из полей обычно имеет 7-8 значений. Я хочу объединить их 3 или 4 новые категории в новую переменную во фрейме данных. Какой подход лучше?

Я бы использовал оператор CASE, если бы был в SQL-подобном инструменте, но не знал, как атаковать это в R.

Мы будем очень благодарны за любую помощь, которую вы можете оказать!


а) Являются ли они целыми, числовыми, категориальными или строковыми? Отправьте пример фрагмента данных, используя dput()b) Вы хотите решение в базе R, dplyr, data.table, tidyverse ...?
smci 02

Ответы:


38

case_when(), который был добавлен в dplyr в мае 2016 года, решает эту проблему аналогично memisc::cases().

Например:

library(dplyr)
mtcars %>% 
  mutate(category = case_when(
    .$cyl == 4 & .$disp < median(.$disp) ~ "4 cylinders, small displacement",
    .$cyl == 8 & .$disp > median(.$disp) ~ "8 cylinders, large displacement",
    TRUE ~ "other"
  )
)

Начиная с dplyr 0.7.0,

mtcars %>% 
  mutate(category = case_when(
    cyl == 4 & disp < median(disp) ~ "4 cylinders, small displacement",
    cyl == 8 & disp > median(disp) ~ "8 cylinders, large displacement",
    TRUE ~ "other"
  )
)

4
Вам не нужно .$перед каждым столбцом.
kath 06

1
Да, начиная с dplyr 0.7.0 (выпущенного 9 июня 2017 г.) в нем .$больше нет необходимости. На момент написания этого ответа это было так.
Эван Кортенс 07

отличное решение. если оба утверждения верны. Второй перезаписывает первый?
JdP

1
@JdP Он работает так же, как CASE WHEN в SQL, поэтому операторы оцениваются по порядку, и результатом является первый оператор TRUE. (Итак, в приведенном выше примере я поставил в конце ИСТИНА, которая служит значением по умолчанию.)
Эван Кортенс,

Мне нравится этот ответ, потому что, в отличие от него switch, он позволяет вам создавать последовательность выражений вместо ключей для случаев.
Dannid

27

Взгляните на casesфункцию из memiscпакета. Он реализует case-функциональность двумя разными способами. Из примеров в пакете:

z1=cases(
    "Condition 1"=x<0,
    "Condition 2"=y<0,# only applies if x >= 0
    "Condition 3"=TRUE
    )

где xи y- два вектора.

Ссылки: пакет memisc , пример кейсов


23

Если есть, factorто можно менять уровни стандартным способом:

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
             stringsAsFactors = FALSE)
df$type <- factor(df$name) # First step: copy vector and make it factor
# Change levels:
levels(df$type) <- list(
    animal = c("cow", "pig"),
    bird = c("eagle", "pigeon")
)
df
#     name   type
# 1    cow animal
# 2    pig animal
# 3  eagle   bird
# 4 pigeon   bird

Вы можете написать простую функцию как оболочку:

changelevels <- function(f, ...) {
    f <- as.factor(f)
    levels(f) <- list(...)
    f
}

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
                 stringsAsFactors = TRUE)

df$type <- changelevels(df$name, animal=c("cow", "pig"), bird=c("eagle", "pigeon"))

1
Хороший ответ. Я забыл, что вы можете использовать список в качестве аргумента для уровней со старым и новым именами, подобными этому; Мое решение зависит от того, кто соблюдает прямой порядок уровней, так что так лучше.
Аарон покинул Stack Overflow

Кроме того, должно быть xв последней строке changelevels?
Аарон покинул Stack Overflow

20

Вот способ использования switchоператора:

df <- data.frame(name = c('cow','pig','eagle','pigeon'), 
                 stringsAsFactors = FALSE)
df$type <- sapply(df$name, switch, 
                  cow = 'animal', 
                  pig = 'animal', 
                  eagle = 'bird', 
                  pigeon = 'bird')

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird

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

myMap <- list(animal = c('cow', 'pig'), bird = c('eagle', 'pigeon'))

и мы хотим как-то «инвертировать» это отображение. Я пишу свою функцию invMap:

invMap <- function(map) {
  items <- as.character( unlist(map) )
  nams <- unlist(Map(rep, names(map), sapply(map, length)))
  names(nams) <- items
  nams
}

а затем инвертируйте приведенную выше карту следующим образом:

> invMap(myMap)
     cow      pig    eagle   pigeon 
"animal" "animal"   "bird"   "bird" 

И тогда это легко использовать, чтобы добавить typeстолбец во фрейм данных:

df <- transform(df, type = invMap(myMap)[name])

> df
    name   type
1    cow animal
2    pig animal
3  eagle   bird
4 pigeon   bird

16

Я не вижу предложения по «переключению». Пример кода (запустите):

x <- "three"
y <- 0
switch(x,
       one = {y <- 5},
       two = {y <- 12},
       three = {y <- 432})
y

14

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

dft=data.frame(x = sample(letters[1:8], 20, replace=TRUE))
dft=within(dft,{
    y=NA
    y[x %in% c('a','b','c')]='abc'
    y[x %in% c('d','e','f')]='def'
    y[x %in% 'g']='g'
    y[x %in% 'h']='h'
})

Мне нравится этот метод. Тем не менее, существует ли реализация «else», поскольку в некоторых случаях это было бы необходимо
T.Fung

2
@ T.Fung Вы можете изменить первую строку на y = 'else'. Элементы, которые не удовлетворяют никаким дополнительным условиям, останутся без изменений.
Грегори Демин

7

Есть switchзаявление, но мне никогда не удается заставить его работать так, как я думаю. Поскольку вы не привели пример, я сделаю его, используя факторную переменную:

 dft <-data.frame(x = sample(letters[1:8], 20, replace=TRUE))
 levels(dft$x)
[1] "a" "b" "c" "d" "e" "f" "g" "h"

Если вы укажете нужные категории в порядке, соответствующем переназначению, вы можете использовать факторные или числовые переменные в качестве индекса:

c("abc", "abc", "abc", "def", "def", "def", "g", "h")[dft$x]
 [1] "def" "h"   "g"   "def" "def" "abc" "h"   "h"   "def" "abc" "abc" "abc" "h"   "h"   "abc"
[16] "def" "abc" "abc" "def" "def"

dft$y <- c("abc", "abc", "abc", "def", "def", "def", "g", "h")[dft$x] str(dft)
'data.frame':   20 obs. of  2 variables:
 $ x: Factor w/ 8 levels "a","b","c","d",..: 4 8 7 4 6 1 8 8 5 2 ...
 $ y: chr  "def" "h" "g" "def" ...

Позже я узнал, что на самом деле есть две разные функции переключателя. Это не общая функция, но вы должны думать о ней как о switch.numericили switch.character. Если ваш первый аргумент - это «фактор» R, вы получите switch.numericповедение, которое может вызвать проблемы, поскольку большинство людей видят факторы, отображаемые как символы, и делают неверное предположение, что все функции будут обрабатывать их как таковые.


6

Вы можете использовать перекодировку из пакета автомобиля:

library(ggplot2) #get data
library(car)
daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]

11
Я просто не могу поддерживать функцию, которая анализирует параметры из текста
Хадли

Да, но знаете ли вы, написал ли кто-нибудь лучшую версию? sos::findFn("recode")Находки doBy::recodeVar, epicalc::recode, memisc::recode, но я не смотрел на них подробно ...
Бен Bolker

5

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

result <- ( function() { if (x==10 | y< 5) return('foo') 
                         if (x==11 & y== 5) return('bar')
                        })()

все эти () необходимы для включения и оценки анонимной функции.


6
1) Функциональная часть не нужна; ты мог бы просто сделать result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' ). 2) Это работает, только если xи yявляются скалярами; для векторов, как и в исходном вопросе, ifelseпотребуются вложенные операторы.
Аарон покинул Stack Overflow

4

Я использую в тех случаях, о которых вы говорите switch(). Это похоже на оператор управления, но на самом деле это функция. Выражение вычисляется, и на основе этого значения возвращается соответствующий элемент в списке.

switch работает двумя разными способами в зависимости от того, оценивает ли первый аргумент строку символов или число.

Далее следует простой пример строки, который решает вашу проблему сворачивания старых категорий в новые.

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

newCat <- switch(EXPR = category,
       cat1   = catX,
       cat2   = catX,
       cat3   = catY,
       cat4   = catY,
       cat5   = catZ,
       cat6   = catZ,
       "not available")

3

Если вы хотите иметь синтаксис, подобный sql, вы можете просто использовать sqldfpackage. Используемая функция также имеет имена sqldfи имеет следующий синтаксис

sqldf(<your query in quotation marks>)

2

На самом деле оператор case может быть здесь неправильным подходом. Если это фактор, который вероятен, просто установите соответствующие уровни фактора.

Допустим, у вас есть множитель с буквами от A до E, например этот.

> a <- factor(rep(LETTERS[1:5],2))
> a
 [1] A B C D E A B C D E
Levels: A B C D E

Чтобы присоединиться к уровням B и C и назвать их BC, просто измените названия этих уровней на BC.

> levels(a) <- c("A","BC","BC","D","E")
> a
 [1] A  BC BC D  E  A  BC BC D  E 
Levels: A BC D E

Результат желаемый.


2

Сведение так plyr::mutate и dplyr::case_whenработает у меня и читается.

iris %>%
plyr::mutate(coolness =
     dplyr::case_when(Species  == "setosa"     ~ "not cool",
                      Species  == "versicolor" ~ "not cool",
                      Species  == "virginica"  ~ "super awesome",
                      TRUE                     ~ "undetermined"
       )) -> testIris
head(testIris)
levels(testIris$coolness)  ## NULL
testIris$coolness <- as.factor(testIris$coolness)
levels(testIris$coolness)  ## ok now
testIris[97:103,4:6]

Бонусные баллы, если столбец может быть изменен как фактор вместо символа! Последняя строка оператора case_when, которая захватывает все несовпадающие строки, очень важна.

     Petal.Width    Species      coolness
 97         1.3  versicolor      not cool
 98         1.3  versicolor      not cool  
 99         1.1  versicolor      not cool
100         1.3  versicolor      not cool
101         2.5  virginica     super awesome
102         1.9  virginica     super awesome
103         2.1  virginica     super awesome

2

Вы можете использовать эту baseфункцию mergeдля задач переназначения стиля регистра:

df <- data.frame(name = c('cow','pig','eagle','pigeon','cow','eagle'), 
                 stringsAsFactors = FALSE)

mapping <- data.frame(
  name=c('cow','pig','eagle','pigeon'),
  category=c('mammal','mammal','bird','bird')
)

merge(df,mapping)
# name category
# 1    cow   mammal
# 2    cow   mammal
# 3  eagle     bird
# 4  eagle     bird
# 5    pig   mammal
# 6 pigeon     bird

1

Начиная с data.table v1.13.0, вы можете использовать функцию fcase()(fast-case) для выполнения SQL-подобных CASEопераций (также похожих на dplyr::case_when()):

require(data.table)

dt <- data.table(name = c('cow','pig','eagle','pigeon','cow','eagle'))
dt[ , category := fcase(name %in% c('cow', 'pig'), 'mammal',
                        name %in% c('eagle', 'pigeon'), 'bird') ]
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.