Странное поведение между функциями cut и ifelse в R

Я работаю в R с фреймом данных, состоящим из числовой переменной и символьной переменной. Мой фрейм данных DF выглядит так (я добавляю версию dput в заключительной части):

   a1    b1
1   a 10.15
2   a 25.10
3   a 32.40
4   a 56.70
5   a 89.02
6   b 90.50
7   b 78.53
8   b 98.12
9   b 34.30
10  b 99.75 

В DF переменная a1 является групповой переменной, а b1 — числовой переменной. Тогда появляется дилемма. Я хочу создать новую переменную с именем c1, используя функцию cut и учитывая группу, сохраненную в a1. По этой причине я объединяю обе функции ifelse() и cut() в следующей строке кода:

DF$c1=ifelse(DF$a1=="a",
                cut(DF$b1,breaks = c(0,25,50,70,max(DF$b1)),right = TRUE,include.lowest = TRUE),
                ifelse(DF$a1=="b",
                       cut(DF$b1,breaks = c(0,50,max(DF$b1)),right = TRUE,include.lowest = TRUE),NA))

Строка кода работала нормально, но новые значения, созданные в c1, приводят к путанице. Вместо того, чтобы показывать фактор, cut() возвращает целые числа. Затем я получил этот результат:

table(DF$c1,exclude=NULL)

   1    2    3    4 <NA> 
   2    6    1    1    0

Несмотря на создание разрывов, целые числа, выделенные в c1, меняют результат. Этого не происходит, когда я работаю без ifelse, но в этом случае я не согласовываю условия над группой. Например, следующая строка кода возвращает такой результат:

DF$c1=cut(DF$b1,breaks = c(0,25,50,70,max(DF$b1)),right = TRUE,include.lowest = TRUE)

table(DF$c1,exclude=NULL)

   [0,25]   (25,50]   (50,70] (70,99.8]      <NA> 
        1         3         1         5         0 

Я хотел бы знать, как разрешить это поведение между функциями ifelse() и cut(), потому что возвращаемые целые числа приводят к различиям в конечном результате. В этом примере я работаю только с двумя группами для переменной a1, но у меня большая база данных со многими группами. Вот почему я комбинирую функции, чтобы получить разные нарезки для каждой группы. Также могут меняться значения разрывов, поэтому включение меток вручную может занять много времени. Возможно ли, что комбинация этих двух функций возвращает правильные метки для каждой группы (фактора) вместо целых чисел. Версия dput() моего фрейма данных DF следующая:

DF<-structure(list(a1 = c("a", "a", "a", "a", "a", "b", "b", "b", 
"b", "b"), b1 = c(10.15, 25.1, 32.4, 56.7, 89.02, 90.5, 78.53, 
98.12, 34.3, 99.75)), .Names = c("a1", "b1"), row.names = c(NA, 
-10L), class = "data.frame")

Спасибо за вашу помощь!


person Duck    schedule 23.01.2016    source источник


Ответы (3)


Проблема в том, что оба cut() выводят множитель, но поскольку они имеют разные уровни, они принудительно преобразуются в целое число. Решение может состоять в том, чтобы окружить ваш cut() as.character(), тем самым сохранив уровни для принуждения, а затем factor() весь вывод:

DF$c1=factor(ifelse(DF$a1=="a",
             as.character(cut(DF$b1,breaks = c(0,25,50,70,max(DF$b1)),right = TRUE,include.lowest = TRUE)),
             ifelse(DF$a1=="b",
                    as.character(cut(DF$b1,breaks = c(0,50,max(DF$b1)),right = TRUE,include.lowest = TRUE)),NA)))

DF

   a1    b1        c1
1   a 10.15    [0,25]
2   a 25.10   (25,50]
3   a 32.40   (25,50]
4   a 56.70   (50,70]
5   a 89.02 (70,99.8]
6   b 90.50 (50,99.8]
7   b 78.53 (50,99.8]
8   b 98.12 (50,99.8]
9   b 34.30    [0,50]
10  b 99.75 (50,99.8]
person scoa    schedule 23.01.2016
comment
Да, но как сортировать фактор? - person Alfredo G Marquez; 08.12.2017
comment
что ты имеешь в виду? Сортировать строки во фрейме данных? или уровни? - person scoa; 09.12.2017
comment
Когда вы конвертируете в символ, разрывы множителей не выстраиваются должным образом, поэтому, когда вы делаете средние значения с1, порядок множителей не упорядочивается должным образом. Или, когда вы рисуете, используя фактор как переменную x, порядок неправильный. - person Alfredo G Marquez; 09.12.2017
comment
Ok. Вам нужно будет повысить уровень, добавив аргумент levels к factor(). Поскольку мы не можем быть уверены, какими будут точные уровни, вам нужно будет повозиться с этим, но простое добавление DF$c1 <- factor(DF$c1, levels = sort(levels(DF$c1))) должно помочь. - person scoa; 12.12.2017

@scoa прав; вы пытаетесь объединить два фактора с разными уровнями, поэтому ваши результаты приводятся к целым числам, и вы теряете уровни. Вот еще один подход с меньшим форм-фактором, который будет более масштабируемым.

Во-первых, составьте именованный список всех ваших перерывов:

breaks <- list('a' = c(0, 25, 50, 70, max(DF$b1)), 'b' = c(0, 50, max(DF$b1)))
breaks

> $a
>     0 25 50 70 99.75 
> $b
>     0 50 99.75 

Затем используйте unlist(list(some, factors)) (или в данном случае lapply), который аккуратно объединяет факторы, сохраняя все уровни. (Это своего рода волшебство; это одна из тех встроенных функций, которая на самом деле не очевидна.)

DF$c1 <- unlist(lapply(1:length(breaks), 
                   function(x){cut(DF[DF$a1 == names(breaks[x]), 'b1'], 
                                   breaks = breaks[[x]], 
                                   right = TRUE, 
                                   include.lowest = TRUE)}
                   ))
DF

>    a1    b1        c1
> 1   a 10.15    [0,25]
> 2   a 25.10   (25,50]
> 3   a 32.40   (25,50]
> 4   a 56.70   (50,70]
> 5   a 89.02 (70,99.8]
> 6   b 90.50 (50,99.8]
> 7   b 78.53 (50,99.8]
> 8   b 98.12 (50,99.8]
> 9   b 34.30    [0,50]
> 10  b 99.75 (50,99.8]

В конечном счете, это две строки кода, и он должен быть устойчивым к большему и сложному набору данных.

person alistaire    schedule 23.01.2016

Это не прямой ответ на ваш вопрос, а скорее альтернативный подход к общей задаче.

Поскольку у вас есть «большая база данных со многими группами, [с] разными сокращениями для каждой группы», мне кажется, что код с множеством вложенных ifelse вскоре может стать довольно запутанным. Возможно, дело вкуса, но я думаю, что код будет легче читать и поддерживать, если вместо этого хранить breaks для каждой группы в отдельной таблице.

Вот как это можно сделать с помощью data.table:

library(data.table)
dt_brk <- data.table(grp = c("a", "a", "a", "a", "a", "b", "b", "b"),
                     brk = c(0, 25, 50, 70, Inf, 0, 50, Inf))

Обратите внимание, что я использую Inf в качестве верхнего предела разрывов, а не max(your-values)

Мы преобразуем ваш фрейм данных "DF" в data.table с помощью setDT. Затем для каждого уровня "a1" (by = a1) мы cut "b1", используя breaks из "dt_brk", где "grp" равно "a1" (dt_brk[grp == a1, brk]).

setDT(DF)[, c1 := as.character(cut(b1, breaks = dt_brk[grp == a1, brk])), by = a1]

DF
#     a1    b1       c1
# 1:   a 10.15   (0,25]
# 2:   a 25.10  (25,50]
# 3:   a 32.40  (25,50]
# 4:   a 56.70  (50,70]
# 5:   a 89.02 (70,Inf]
# 6:   b 90.50 (50,Inf]
# 7:   b 78.53 (50,Inf]
# 8:   b 98.12 (50,Inf]
# 9:   b 34.30   (0,50]
# 10:  b 99.75 (50,Inf]
person Henrik    schedule 23.01.2016