Automatyczne rozszerzanie czynnika R do zbioru zmiennych wskaźnikowych 1/0 dla każdego poziomu czynnika


108

Mam ramkę danych R zawierającą czynnik, który chcę „rozszerzyć”, tak aby dla każdego poziomu czynnika była powiązana kolumna w nowej ramce danych, która zawiera wskaźnik 1/0. Np. Załóżmy, że mam:

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))

Chcę:

df.desired  <- data.frame(foo = c(1,1,0,0), bar=c(0,0,1,1), ham=c(1,2,3,4))

Ponieważ w przypadku niektórych analiz, do których trzeba mieć całkowicie numeryczną ramkę danych (np. Analiza głównych składowych), pomyślałem, że ta funkcja może być wbudowana. Napisanie funkcji, która to zrobi, nie powinno być zbyt trudne, ale mogę przewidzieć kilka wyzwania związane z nazwami kolumn i jeśli coś już istnieje, wolałbym tego użyć.

Odpowiedzi:


131

Użyj model.matrixfunkcji:

model.matrix( ~ Species - 1, data=iris )

1
Czy mogę tylko dodać, że ta metoda była o wiele szybsza niż użycie castdla mnie.
Matt Weller,

3
@GregSnow Sprawdziliśmy 2nd akapit ?formula, jak również ?model.matrix, ale nie było jasne (może być tylko mój brak dogłębnej wiedzy w algebrze macierzy i modelowego preparatu). Po dokładniejszym przeszukaniu, udało mi się ustalić, że -1 oznacza po prostu nie uwzględnianie kolumny „przecięcie”. Jeśli pominiesz -1, zobaczysz na wyjściu kolumnę przecięcia z 1 z jedną kolumną binarną pominiętą. Możesz zobaczyć, które wartości pominiętej kolumny są 1 na podstawie wierszy, w których wartości innych kolumn są równe 0. Dokumentacja wydaje się tajemnicza - czy jest inny dobry zasób?
Ryan Chase

1
@RyanChase, istnieje wiele samouczków i książek na temat R / S (kilka z nich ma krótkie opisy na stronie r-project.org). Moja własna nauka S i R była raczej eklektyczna (i długa), więc nie jestem najlepszy, gdybym wyrażał opinię na temat tego, jak obecne książki / samouczki są atrakcyjne dla początkujących. Jestem jednak fanem eksperymentowania. Wypróbowanie czegoś w nowej sesji R może być bardzo pouczające i nie niebezpieczne (najgorsze, co mi się przydarzyło, to awaria R, i to rzadko, co prowadzi do ulepszeń w R). Stackoverflow jest wtedy dobrym źródłem informacji o tym, co się stało.
Greg Snow

7
A jeśli chcesz przekonwertować wszystkie kolumny współczynników, możesz użyć:model.matrix(~., data=iris)[,-1]
user890739

1
@colin, Nie w pełni automatyczny, ale możesz użyć, naresidaby ponownie wstawić brakujące wartości po użyciu na.exclude. Szybki przykład:tmp <- data.frame(x=factor(c('a','b','c',NA,'a'))); tmp2 <- na.exclude(tmp); tmp3 <- model.matrix( ~x-1, tmp2); tmp4 <- naresid(attr(tmp2,'na.action'), tmp3)
Greg Snow

17

Jeśli twoja ramka danych składa się tylko z czynników (lub pracujesz nad podzbiorem zmiennych, które są wszystkimi czynnikami), możesz również użyć acm.disjonctiffunkcji z ade4pakietu:

R> library(ade4)
R> df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c("red","blue","green","red"))
R> acm.disjonctif(df)
  eggs.bar eggs.foo ham.blue ham.green ham.red
1        0        1        0         0       1
2        0        1        1         0       0
3        1        0        0         1       0
4        1        0        0         0       1

Nie jest to dokładnie ten przypadek, który opisujesz, ale może być też przydatny ...


Dzięki, bardzo mi to pomogło, ponieważ zużywa mniej pamięci niż model.matrix!
Serhiy

Podoba mi się sposób nazywania zmiennych; Nie podoba mi się to, że są zwracane jako żądne przechowywania wartości liczbowe, gdy powinny (IMHO) być po prostu logiczne.
dsz

9

Szybki sposób korzystania z reshape2pakietu:

require(reshape2)

> dcast(df.original, ham ~ eggs, length)

Using ham as value column: use value_var to override.
  ham bar foo
1   1   0   1
2   2   0   1
3   3   1   0
4   4   1   0

Zauważ, że daje to dokładnie takie nazwy kolumn, jakie chcesz.


Dobry. Ale uważaj na duplikat szynki. powiedzmy, d <- data.frame (eggs = c ("foo", "bar", "foo"), ham = c (1,2,1)); dcast (d, szynka ~ jajka, długość) sprawia, że ​​foo = 2.
kohske

@Kohske, to prawda, ale zakładałem, że hamto unikalny identyfikator wiersza. Jeśli hamnie jest unikalnym identyfikatorem, należy użyć innego unikalnego identyfikatora (lub utworzyć fikcyjny) i użyć go zamiast ham. Przekształcenie etykiety kategorialnej na wskaźnik binarny miałoby sens tylko w przypadku unikalnych identyfikatorów.
Prasad Chalasani,

6

prawdopodobnie zmienna zastępcza jest podobna do tego, czego chcesz. Wtedy przydaje się model.matrix:

> with(df.original, data.frame(model.matrix(~eggs+0), ham))
  eggsbar eggsfoo ham
1       0       1   1
2       0       1   2
3       1       0   3
4       1       0   4

6

Późne wejście class.indz nnetpaczki

library(nnet)
 with(df.original, data.frame(class.ind(eggs), ham))
  bar foo ham
1   0   1   1
2   0   1   2
3   1   0   3
4   1   0   4

4

Właśnie natknąłem się na ten stary wątek i pomyślałem, że dodam funkcję, która wykorzystuje ade4 do pobrania ramki danych składającej się z czynników i / lub danych liczbowych i zwraca ramkę danych z czynnikami jako fałszywymi kodami.

dummy <- function(df) {  

    NUM <- function(dataframe)dataframe[,sapply(dataframe,is.numeric)]
    FAC <- function(dataframe)dataframe[,sapply(dataframe,is.factor)]

    require(ade4)
    if (is.null(ncol(NUM(df)))) {
        DF <- data.frame(NUM(df), acm.disjonctif(FAC(df)))
        names(DF)[1] <- colnames(df)[which(sapply(df, is.numeric))]
    } else {
        DF <- data.frame(NUM(df), acm.disjonctif(FAC(df)))
    }
    return(DF)
} 

Spróbujmy.

df <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
            ham = c("red","blue","green","red"), x=rnorm(4))     
dummy(df)

df2 <-data.frame(eggs = c("foo", "foo", "bar", "bar"), 
            ham = c("red","blue","green","red"))  
dummy(df2)

3

Oto bardziej przejrzysty sposób, aby to zrobić. Używam model.matrix do tworzenia fikcyjnych zmiennych boolowskich, a następnie łączę je z powrotem z oryginalną ramką danych.

df.original <-data.frame(eggs = c("foo", "foo", "bar", "bar"), ham = c(1,2,3,4))
df.original
#   eggs ham
# 1  foo   1
# 2  foo   2
# 3  bar   3
# 4  bar   4

# Create the dummy boolean variables using the model.matrix() function.
> mm <- model.matrix(~eggs-1, df.original)
> mm
#   eggsbar eggsfoo
# 1       0       1
# 2       0       1
# 3       1       0
# 4       1       0
# attr(,"assign")
# [1] 1 1
# attr(,"contrasts")
# attr(,"contrasts")$eggs
# [1] "contr.treatment"

# Remove the "eggs" prefix from the column names as the OP desired.
colnames(mm) <- gsub("eggs","",colnames(mm))
mm
#   bar foo
# 1   0   1
# 2   0   1
# 3   1   0
# 4   1   0
# attr(,"assign")
# [1] 1 1
# attr(,"contrasts")
# attr(,"contrasts")$eggs
# [1] "contr.treatment"

# Combine the matrix back with the original dataframe.
result <- cbind(df.original, mm)
result
#   eggs ham bar foo
# 1  foo   1   0   1
# 2  foo   2   0   1
# 3  bar   3   1   0
# 4  bar   4   1   0

# At this point, you can select out the columns that you want.

0

Potrzebowałem funkcji do „eksplodowania” czynników, która jest nieco bardziej elastyczna, i utworzyłem ją na podstawie funkcji acm.disjonctif z pakietu ade4. Pozwala to na wybranie rozstrzelonych wartości, które są równe 0 i 1 w acm.disjonctif. Rozbija tylko czynniki, które mają „kilka” poziomów. Kolumny numeryczne są zachowane.

# Function to explode factors that are considered to be categorical,
# i.e., they do not have too many levels.
# - data: The data.frame in which categorical variables will be exploded.
# - values: The exploded values for the value being unequal and equal to a level.
# - max_factor_level_fraction: Maximum number of levels as a fraction of column length. Set to 1 to explode all factors.
# Inspired by the acm.disjonctif function in the ade4 package.
explode_factors <- function(data, values = c(-0.8, 0.8), max_factor_level_fraction = 0.2) {
  exploders <- colnames(data)[sapply(data, function(col){
      is.factor(col) && nlevels(col) <= max_factor_level_fraction * length(col)
    })]
  if (length(exploders) > 0) {
    exploded <- lapply(exploders, function(exp){
        col <- data[, exp]
        n <- length(col)
        dummies <- matrix(values[1], n, length(levels(col)))
        dummies[(1:n) + n * (unclass(col) - 1)] <- values[2]
        colnames(dummies) <- paste(exp, levels(col), sep = '_')
        dummies
      })
    # Only keep numeric data.
    data <- data[sapply(data, is.numeric)]
    # Add exploded values.
    data <- cbind(data, exploded)
  }
  return(data)
}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.