Виклад справи еквівалентний у R


87

У мене є змінна у фреймі даних, де одне з полів зазвичай має 7-8 значень. Я хочу зібрати їм 3 або 4 нові категорії в межах нової змінної в рамках даних. Який найкращий підхід?

Я б використовував вираз CASE, якби працював у SQL-подібному інструменті, але не знав, як атакувати це в R.

Будь-яка допомога, яку ви можете надати, буде дуже вдячна!


а) Вони цілі, чисельні, категоріальні чи рядкові? Будь-ласка, опублікуйте приклад фрагмента даних, використовуючи dput()b) Чи хочете ви отримати рішення в основі R, dplyr, data.table, tidyverse ...?
smci

Відповіді:


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

1
Так, станом на dplyr 0.7.0 (випущено 9 червня 2017 р.) .$Це більше не потрібно. На момент написання цієї відповіді вона була.
Еван Кортенс

відмінне рішення. якщо обидва твердження відповідають дійсності. Другий перезаписує перший?
JdP

1
@JdP Це працює так само, як CASE WHEN у SQL, тому оператори обчислюються в порядку, і результат є першим оператором TRUE. (Отже, у прикладі вище, я в кінці
вказав

Мені подобається ця відповідь, тому що, на відміну від цього switch, вона дозволяє створити послідовність виразів замість ключів для регістрів.
Даннід

27

Погляньте на casesфункцію з memiscупаковки. Він реалізує функціонал справи з двома різними способами його використання. З прикладів у пакеті:

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
Приємна відповідь. Я забув, що ти можеш використовувати список як аргумент рівнях зі старими та новими іменами; моє рішення залежить від того, хто дотримується порядку рівнів, так що це краще в такий спосіб.
Аарон залишив переповнення стеку

Крім того, чи має бути xв останньому рядку changelevels?
Аарон залишив переповнення стеку

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'
})

Мені подобається цей метод. Однак чи існує інша реалізація, оскільки за певних обставин це було б обов’язково
Т. Фунг,

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
Я просто не можу підтримати функцію, яка аналізує його параметри з тексту
Hadley

Так, але чи знаєте ви, чи хтось писав кращу версію? sos::findFn("recode")Знахідки doBy::recodeVar, epicalc::recode, memisc::recode, але я не дивився на них докладно ...
Бен Bolker

5

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

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були б необхідні вкладені оператори.
Аарон залишив переповнення стека

4

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

switch працює двома різними способами, залежно від того, чи обчислюється перший аргумент як рядок символів або число.

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

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

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

3

Якщо ви хочете мати синтаксис, подібний до sql, ви можете просто скористатися sqldfпакетом. Використовувана функція - це також імена, sqldfа синтаксис такий

sqldf(<your query in quotation marks>)

2

Виклад справи фактично може бути не правильним підходом тут. Якщо це фактор, який, швидше за все, є, просто встановіть рівні фактора відповідним чином.

Скажімо, у вас є коефіцієнт з літерами від А до Е, ось такий.

> 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.