Konwertuj klasy kolumn w data.table

118

Mam problem z użyciem data.table: Jak przekonwertować klasy kolumn? Oto prosty przykład: z data.frame nie mam problemu z konwersją, z data.table po prostu nie wiem jak:

df <- data.frame(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
#One way: http://stackoverflow.com/questions/2851015/r-convert-data-frame-columns-from-factors-to-characters
df <- data.frame(lapply(df, as.character), stringsAsFactors=FALSE)
#Another way
df[, "value"] <- as.numeric(df[, "value"])

library(data.table)
dt <- data.table(ID=c(rep("A", 5), rep("B",5)), Quarter=c(1:5, 1:5), value=rnorm(10))
dt <- data.table(lapply(dt, as.character), stringsAsFactors=FALSE) 
#Error in rep("", ncol(xi)) : invalid 'times' argument
#Produces error, does data.table not have the option stringsAsFactors?
dt[, "ID", with=FALSE] <- as.character(dt[, "ID", with=FALSE]) 
#Produces error: Error in `[<-.data.table`(`*tmp*`, , "ID", with = FALSE, value = "c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2)") : 
#unused argument(s) (with = FALSE)

Czy brakuje mi czegoś oczywistego?

Aktualizacja z powodu posta Matthew: Wcześniej korzystałem ze starszej wersji, ale nawet po aktualizacji do 1.6.6 (wersja, której używam teraz) nadal pojawia się błąd.

Aktualizacja 2: Powiedzmy, że chcę przekonwertować każdą kolumnę „współczynnika” klasy na kolumnę „znakową”, ale nie wiem z góry, która kolumna należy do której klasy. Za pomocą data.frame mogę wykonać następujące czynności:

classes <- as.character(sapply(df, class))
colClasses <- which(classes=="factor")
df[, colClasses] <- sapply(df[, colClasses], as.character)

Czy mogę zrobić coś podobnego z data.table?

Aktualizacja 3:

sessionInfo () R wersja 2.13.1 (2011-07-08) Platforma: x86_64-pc-mingw32 / x64 (64-bit)

locale:
[1] C

attached base packages:
[1] stats     graphics  grDevices utils     datasets  methods   base     

other attached packages:
[1] data.table_1.6.6

loaded via a namespace (and not attached):
[1] tools_2.13.1
Christoph_J
źródło
Argumenty operatora „[” w data.tablemetodach są inne niż w przypadkudata.frame
IRTFM
1
Wklej raczej rzeczywisty błąd niż #Produces error. I tak +1. Nie widzę żadnego błędu, którą wersję masz? Istnieje jednak problem w tym obszarze, został już wcześniej poruszony, a FR # 1224 i FR # 1493 mają wysoki priorytet do rozwiązania. Jednak odpowiedź Andrie jest najlepsza.
Matt Dowle,
Przepraszam @MatthewDowle za pominięcie tego w moim pytaniu, zaktualizowałem mój post.
Christoph_J,
1
@Christoph_J Thanks. Czy jesteś pewien co do tego invalid times argumentbłędu? Pracuj dobrze dla mnie. Którą wersję masz?
Matt Dowle,
Zaktualizowałem swój post za pomocą sessionInfo (). Jednak dzisiaj sprawdziłem to na mojej maszynie roboczej. Wczoraj na moim komputerze domowym (Ubuntu) wystąpił ten sam błąd. Zaktualizuję R i zobaczę, czy problem nadal występuje.
Christoph_J,

Odpowiedzi:

104

Dla pojedynczej kolumny:

dtnew <- dt[, Quarter:=as.character(Quarter)]
str(dtnew)

Classes ‘data.table’ and 'data.frame':  10 obs. of  3 variables:
 $ ID     : Factor w/ 2 levels "A","B": 1 1 1 1 1 2 2 2 2 2
 $ Quarter: chr  "1" "2" "3" "4" ...
 $ value  : num  -0.838 0.146 -1.059 -1.197 0.282 ...

Korzystanie lapplyi as.character:

dtnew <- dt[, lapply(.SD, as.character), by=ID]
str(dtnew)

Classes ‘data.table’ and 'data.frame':  10 obs. of  3 variables:
 $ ID     : Factor w/ 2 levels "A","B": 1 1 1 1 1 2 2 2 2 2
 $ Quarter: chr  "1" "2" "3" "4" ...
 $ value  : chr  "1.487145280568" "-0.827845218358881" "0.028977182770002" "1.35392750102305" ...
Andrie
źródło
2
@Christoph_J Pokaż polecenie grupowania, z którym się zmagasz (prawdziwy problem). Pomyśl, że mogłeś przegapić coś prostego. Dlaczego próbujesz przekonwertować klasy kolumn?
Matt Dowle,
1
@Christoph_J Jeśli masz problemy z manipulowaniem danymi.tables, dlaczego nie przekonwertować ich tymczasowo na dane.frames, wyczyść dane, a następnie przekonwertuj je z powrotem na dane.tables?
Andrie,
17
Jaki jest idiomatyczny sposób zrobienia tego dla podzbioru kolumn (zamiast wszystkich)? Zdefiniowałem wektor znaków convcolskolumn. dt[,lapply(.SD,as.numeric),.SDcols=convcols]jest prawie natychmiastowy, a dt[,convcols:=lapply(.SD,as.numeric),.SDcols=convcols]prawie zawiesza R, więc domyślam się, że robię to źle. Dzięki
Frank
4
@Frank Zobacz komentarz Matta Dowle'a do odpowiedzi Geneoramy poniżej ( stackoverflow.com/questions/7813578/… ); to było dla mnie pomocne i wystarczająco idiomatyczne [początek cytatu] Innym i łatwiejszym sposobem jest użycie set()np. for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))[end quote]
swihart
4
Dlaczego używasz opcji by = ID?
skan
48

Spróbuj tego

DT <- data.table(X1 = c("a", "b"), X2 = c(1,2), X3 = c("hello", "you"))
changeCols <- colnames(DT)[which(as.vector(DT[,lapply(.SD, class)]) == "character")]

DT[,(changeCols):= lapply(.SD, as.factor), .SDcols = changeCols]
Nera
źródło
7
teraz możesz użyć Filterfunkcji do identyfikacji kolumn, na przykład: changeCols<- names(Filter(is.character, DT))
David Leal
1
IMO to jest lepsza odpowiedź, z powodu, który podałem w wybranej odpowiedzi.
James Hirschorn,
1
lub bardziej zwięźle: changeCols <- names(DT)[sapply(DT, is.character)].
sindri_baldur
8

Podnosząc komentarz Matta Dowle do odpowiedzi Geneoramy ( https://stackoverflow.com/a/20808945/4241780 ), aby uczynić ją bardziej oczywistą (zgodnie z zaleceniami ), możesz użyć for(...)set(...).


library(data.table)

DT = data.table(a = LETTERS[c(3L,1:3)], b = 4:7, c = letters[1:4])
DT1 <- copy(DT)
names_factors <- c("a", "c")

for(col in names_factors)
  set(DT, j = col, value = as.factor(DT[[col]]))

sapply(DT, class)
#>         a         b         c 
#>  "factor" "integer"  "factor"

Utworzony 12.02.2020 przez pakiet reprex reprex (v0.3.0)

Zobacz inny komentarz Matta pod adresem https://stackoverflow.com/a/33000778/4241780, aby uzyskać więcej informacji.

Edytować.

Jak zauważyli Espen i in help(set), jmoże to być „Nazwa (y) kolumny (znaki) (znak) lub liczba (y) (liczba całkowita), do której ma zostać przypisana wartość, gdy kolumny już istnieją”. Tak names_factors <- c(1L, 3L)też będzie działać.

JWilliman
źródło
Możesz dodać, co names_factorstu jest. Wydaje mi się, że pochodzi ze stackoverflow.com/a/20808945/1666063, więc names_factors = c('fac1', 'fac2')w tym przypadku jest to nazwa kolumn, ale mogą to być również numery kolumn, na przykład 1; ncol (dt), które konwertują wszystkie kolumny
Espen Riskedal
@EspenRiskedal Dzięki słusznej uwadze, zredagowałem post, aby był bardziej oczywisty.
JWilliman
2

To ZŁY sposób na zrobienie tego! Zostawiam tę odpowiedź tylko na wypadek, gdyby rozwiązała inne dziwne problemy. Te lepsze metody są prawdopodobnie częściowo wynikiem nowszych wersji data.table ... więc warto udokumentować ten trudny sposób. Poza tym jest to ładny przykład eval substituteskładni.

library(data.table)
dt <- data.table(ID = c(rep("A", 5), rep("B",5)), 
                 fac1 = c(1:5, 1:5), 
                 fac2 = c(1:5, 1:5) * 2, 
                 val1 = rnorm(10),
                 val2 = rnorm(10))

names_factors = c('fac1', 'fac2')
names_values = c('val1', 'val2')

for (col in names_factors){
  e = substitute(X := as.factor(X), list(X = as.symbol(col)))
  dt[ , eval(e)]
}
for (col in names_values){
  e = substitute(X := as.numeric(X), list(X = as.symbol(col)))
  dt[ , eval(e)]
}

str(dt)

co ci daje

Classes ‘data.table’ and 'data.frame':  10 obs. of  5 variables:
 $ ID  : chr  "A" "A" "A" "A" ...
 $ fac1: Factor w/ 5 levels "1","2","3","4",..: 1 2 3 4 5 1 2 3 4 5
 $ fac2: Factor w/ 5 levels "2","4","6","8",..: 1 2 3 4 5 1 2 3 4 5
 $ val1: num  0.0459 2.0113 0.5186 -0.8348 -0.2185 ...
 $ val2: num  -0.0688 0.6544 0.267 -0.1322 -0.4893 ...
 - attr(*, ".internal.selfref")=<externalptr> 
geneorama
źródło
42
Innym i łatwiejszym sposobem jest użycie set()np.for (col in names_factors) set(dt, j=col, value=as.factor(dt[[col]]))
Matt Dowle
1
Myślę, że moja odpowiedź osiąga to w jednym wierszu, dla wszystkich wersji. Nie jestem jednak pewien, czy setjest bardziej odpowiednie.
Ben Rollert,
1
Więcej informacji for(...)set(...)tutaj: stackoverflow.com/a/33000778/403310
Matt Dowle
1
@skan Dobre pytanie. Jeśli nie możesz znaleźć tego zadanego wcześniej, zadaj nowe pytanie. Pomaga innym w przyszłości.
Matt Dowle,
1
@skan, tak to zrobiłem: github.com/geneorama/geneorama/blob/master/R/...
geneorama
0

Próbowałem kilku podejść.

# BY {dplyr}
data.table(ID      = c(rep("A", 5), rep("B",5)), 
           Quarter = c(1:5, 1:5), 
           value   = rnorm(10)) -> df1
df1 %<>% dplyr::mutate(ID      = as.factor(ID),
                       Quarter = as.character(Quarter))
# check classes
dplyr::glimpse(df1)
# Observations: 10
# Variables: 3
# $ ID      (fctr) A, A, A, A, A, B, B, B, B, B
# $ Quarter (chr) "1", "2", "3", "4", "5", "1", "2", "3", "4", "5"
# $ value   (dbl) -0.07676732, 0.25376110, 2.47192852, 0.84929175, -0.13567312,  -0.94224435, 0.80213218, -0.89652819...

, lub w przeciwnym wypadku

# from list to data.table using data.table::setDT
list(ID      = as.factor(c(rep("A", 5), rep("B",5))), 
     Quarter = as.character(c(1:5, 1:5)), 
     value   = rnorm(10)) %>% setDT(list.df) -> df2
class(df2)
# [1] "data.table" "data.frame"
uribo
źródło
0

Zapewniam bardziej ogólny i bezpieczniejszy sposób na zrobienie tego,

".." <- function (x) 
{
  stopifnot(inherits(x, "character"))
  stopifnot(length(x) == 1)
  get(x, parent.frame(4))
}


set_colclass <- function(x, class){
  stopifnot(all(class %in% c("integer", "numeric", "double","factor","character")))
  for(i in intersect(names(class), names(x))){
    f <- get(paste0("as.", class[i]))
    x[, (..("i")):=..("f")(get(..("i")))]
  }
  invisible(x)
}

Funkcja ..zapewnia, że ​​otrzymamy zmienną spoza zakresu data.table; set_colclass ustawi klasy twoich col. Możesz go używać w ten sposób:

dt <- data.table(i=1:3,f=3:1)
set_colclass(dt, c(i="character"))
class(dt$i)
liqg3
źródło
-1

Jeśli masz listę nazw kolumn w data.table, chcesz zmienić klasę do:

convert_to_character <- c("Quarter", "value")

dt[, convert_to_character] <- dt[, lapply(.SD, as.character), .SDcols = convert_to_character]
Emil Lykke Jensen
źródło
Ta odpowiedź jest zasadniczo złą wersją odpowiedzi @ Nery poniżej. Wystarczy dt[, c(convert_to_character) := lapply(.SD, as.character), .SDcols=convert_to_character]przypisać przez odniesienie, zamiast używać wolniejszego przypisywania data.frame.
altabq
-3

próbować:

dt <- data.table(A = c(1:5), 
                 B= c(11:15))

x <- ncol(dt)

for(i in 1:x) 
{
     dt[[i]] <- as.character(dt[[i]])
}
user151444
źródło