Mam zmienną w ramce danych, w której jedno z pól ma zwykle wartości 7-8. Chcę połączyć je z 3 lub 4 nowymi kategoriami w nowej zmiennej w ramce danych. Jakie jest najlepsze podejście?
Użyłbym instrukcji CASE, gdybym był w narzędziu podobnym do SQL, ale nie byłbym pewien, jak zaatakować to w R.
Każda pomoc, której możesz udzielić, będzie bardzo mile widziana!
dput()
b) Czy potrzebujesz rozwiązania w bazie R, dplyr, data.table, tidyverse ...?Odpowiedzi:
case_when()
, który został dodany do dplyr w maju 2016 r., rozwiązuje ten problem w sposób podobny domemisc::cases()
.Na przykład:
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" ) )
Od 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" ) )
źródło
.$
przed każdą kolumną..$
nie jest już potrzebny. W czasie, gdy pierwotnie pisano tę odpowiedź, tak było.switch
niej pozwala na tworzenie sekwencji wyrażeń zamiast kluczy dla spraw.Spójrz na
cases
funkcję zmemisc
pakietu. Implementuje funkcjonalność wielkości liter na dwa różne sposoby. Z przykładów w pakiecie:z1=cases( "Condition 1"=x<0, "Condition 2"=y<0,# only applies if x >= 0 "Condition 3"=TRUE )
gdzie
x
iy
są dwoma wektorami.Referencje: pakiet memisc , przykłady przypadków
źródło
Jeśli tak,
factor
możesz zmienić poziomy za pomocą standardowej metody: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
Możesz napisać prostą funkcję jako opakowanie:
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"))
źródło
x
znajdować się w ostatniej liniichangelevels
?Oto sposób użycia tego
switch
oświadczenia: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
Jedyną wadą jest to, że musisz ciągle wpisywać nazwę kategorii (
animal
itp.) Dla każdego elementu. Składniowo wygodniej jest móc zdefiniować nasze kategorie, jak poniżej (zobacz bardzo podobne pytanie Jak dodać kolumnę w ramce danych w R )myMap <- list(animal = c('cow', 'pig'), bird = c('eagle', 'pigeon'))
i chcemy jakoś „odwrócić” to mapowanie. Piszę własną funkcję invMap:
invMap <- function(map) { items <- as.character( unlist(map) ) nams <- unlist(Map(rep, names(map), sapply(map, length))) names(nams) <- items nams }
a następnie odwróć powyższą mapę w następujący sposób:
> invMap(myMap) cow pig eagle pigeon "animal" "animal" "bird" "bird"
A potem łatwo jest użyć tego, aby dodać
type
kolumnę w ramce danych:df <- transform(df, type = invMap(myMap)[name]) > df name type 1 cow animal 2 pig animal 3 eagle bird 4 pigeon bird
źródło
Nie widzę propozycji „przełącznika”. Przykład kodu (uruchom go):
x <- "three" y <- 0 switch(x, one = {y <- 5}, two = {y <- 12}, three = {y <- 432}) y
źródło
Imho, najprostszy i najbardziej uniwersalny kod:
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' })
źródło
y = 'else'
. Elementy, które nie spełniają dalszych warunków, pozostaną niezmienione.Jest takie
switch
stwierdzenie, ale nigdy nie wydaje mi się, żeby działało tak, jak powinno. Ponieważ nie podałeś przykładu, utworzę go za pomocą zmiennej czynnika:dft <-data.frame(x = sample(letters[1:8], 20, replace=TRUE)) levels(dft$x) [1] "a" "b" "c" "d" "e" "f" "g" "h"
Jeśli określisz żądane kategorie w kolejności odpowiedniej do ponownego przypisania, możesz użyć współczynnika lub zmiennych numerycznych jako indeksu:
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" ...
Później dowiedziałem się, że tak naprawdę są dwie różne funkcje przełączników. To nie jest funkcja ogólna, ale powinieneś o niej myśleć jako albo
switch.numeric
alboswitch.character
. Jeśli pierwszym argumentem jest „czynnik” R, otrzymujeszswitch.numeric
zachowanie, które może powodować problemy, ponieważ większość ludzi postrzega czynniki jako znaki i przyjmuje błędne założenie, że wszystkie funkcje będą je przetwarzać jako takie.źródło
Możesz użyć recode z pakietu samochodowego:
library(ggplot2) #get data library(car) daimons$new_var <- recode(diamonds$clarity , "'I1' = 'low';'SI2' = 'low';else = 'high';")[1:10]
źródło
sos::findFn("recode")
ZnaleziskadoBy::recodeVar
,epicalc::recode
,memisc::recode
, ale nie spojrzał na nich w szczegółach ...nie podoba mi się żadna z nich, nie są one jasne dla czytelnika lub potencjalnego użytkownika. Po prostu używam funkcji anonimowej, składnia nie jest tak zgrabna jak instrukcja case, ale ocena jest podobna do instrukcji case i nie jest tak bolesna. zakłada to również, że oceniasz go w miejscu, w którym zdefiniowane są twoje zmienne.
result <- ( function() { if (x==10 | y< 5) return('foo') if (x==11 & y== 5) return('bar') })()
wszystkie te () są niezbędne do zawarcia i oceny funkcji anonimowej.
źródło
result <- (if (x==10 | y< 5) 'foo' else if (x==11 & y== 5) 'bar' )
. 2) Działa to tylko wtedy, gdyx
iy
są skalarami; w przypadku wektorów, tak jak w pierwotnym pytaniu,ifelse
konieczne byłyby zagnieżdżone stwierdzenia.Używam w tych przypadkach, o których mówisz
switch()
. Wygląda jak instrukcja sterująca, ale w rzeczywistości jest funkcją. Wyrażenie jest oceniane i na podstawie tej wartości zwracany jest odpowiedni element z listy.Poniżej znajduje się prosty przykład łańcucha, który rozwiązuje problem polegający na zwinięciu starych kategorii na nowe.
newCat <- switch(EXPR = category, cat1 = catX, cat2 = catX, cat3 = catY, cat4 = catY, cat5 = catZ, cat6 = catZ, "not available")
źródło
Jeśli chcesz mieć składnię podobną do sql, możesz po prostu użyć
sqldf
pakietu. Funkcją, której należy użyć, są również nazwy,sqldf
a składnia jest następującasqldf(<your query in quotation marks>)
źródło
W rzeczywistości stwierdzenie przypadku może nie być tutaj właściwym podejściem. Jeśli jest to czynnik, który prawdopodobnie ma miejsce, wystarczy odpowiednio ustawić jego poziomy.
Powiedzmy, że masz czynnik z literami od A do E, w ten sposób.
> 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
Aby dołączyć do poziomów B i C i nazwać je BC, wystarczy zmienić nazwy tych poziomów na 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
Wynik jest zgodny z oczekiwaniami.
źródło
Miksowanie
plyr::mutate
idplyr::case_when
działa dla mnie i jest czytelne.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]
Dodatkowe punkty, jeśli kolumna może wyjść z mutacji jako czynnika zamiast znaku! Ostatni wiersz instrukcji case_when, który wyłapuje wszystkie niedopasowane wiersze, jest bardzo ważny.
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
źródło
Możesz użyć tej
base
funkcjimerge
do zadań ponownego odwzorowania wielkości liter: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
źródło
Od wersji data.table v1.13.0 możesz używać funkcji
fcase()
(fast-case) do wykonywaniaCASE
operacji podobnych do SQL (również podobnych dodplyr::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') ]
źródło