Самый быстрый способ заменить NAs в больших данных.стол
У меня большой данные.таблица, со многими отсутствующими значениями, разбросанными по его ~ 200k строкам и 200 столбцам. Я хотел бы повторно кодировать эти значения NA в нули как можно эффективнее.
Я вижу два варианта:
1: преобразование в данные.кадр, и использовать что-то такой
2: какие-то классные данные.вложенные таблицы Параметр команды
Я буду доволен довольно эффективным решением типа 1. Преобразование в данные.кадр а потом вернемся к данным.стол не займет много времени.
7 ответов:
вот решение с помощью данные.таблица ' s
:=
оператор, основываясь на ответах Андри и Рамната.require(data.table) # v1.6.6 require(gdata) # v2.8.2 set.seed(1) dt1 = create_dt(2e5, 200, 0.1) dim(dt1) [1] 200000 200 # more columns than Ramnath's answer which had 5 not 200 f_andrie = function(dt) remove_na(dt) f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un) f_dowle = function(dt) { # see EDIT later for more elegant solution na.replace = function(v,value=0) { v[is.na(v)] = value; v } for (i in names(dt)) eval(parse(text=paste("dt[,",i,":=na.replace(",i,")]"))) } system.time(a_gdata = f_gdata(dt1)) user system elapsed 18.805 12.301 134.985 system.time(a_andrie = f_andrie(dt1)) Error: cannot allocate vector of size 305.2 Mb Timing stopped at: 14.541 7.764 68.285 system.time(f_dowle(dt1)) user system elapsed 7.452 4.144 19.590 # EDIT has faster than this identical(a_gdata, dt1) [1] TRUE
обратите внимание, что f_dowle обновил dt1 по ссылке. Если требуется локальная копия, то явный вызов
copy
функция необходима для создания локальной копии всего набора данных. данные.стол-этоsetkey
,key<-
и:=
не копировать на запись.Далее, давайте посмотрим, где f_dowle тратит свои время.
Rprof() f_dowle(dt1) Rprof(NULL) summaryRprof() $by.self self.time self.pct total.time total.pct "na.replace" 5.10 49.71 6.62 64.52 "[.data.table" 2.48 24.17 9.86 96.10 "is.na" 1.52 14.81 1.52 14.81 "gc" 0.22 2.14 0.22 2.14 "unique" 0.14 1.36 0.16 1.56 ... snip ...
там, я бы сосредоточился на
na.replace
иis.na
, где есть несколько векторных копий и векторных сканирований. Их можно довольно легко устранить, написав небольшой na.замените функцию C, которая обновляетNA
по ссылке в векторе. Это, по крайней мере, вдвое сократит 20 секунд, я думаю. Существует ли такая функция в любом пакете R?причина
f_andrie
не может быть потому, что он копирует всюdt1
, или создает логическую матрицу размером с весь изdt1
, несколько раз. Другие 2 метода работают по одному столбцу за раз (хотя я только кратко посмотрел наNAToUnknown
).EDIT (более элегантное решение, как просил Рамнат в комментариях):
f_dowle2 = function(DT) { for (i in names(DT)) DT[is.na(get(i)), (i):=0] } system.time(f_dowle2(dt1)) user system elapsed 6.468 0.760 7.250 # faster, too identical(a_gdata, dt1) [1] TRUE
я хотел бы сделать это таким образом, чтобы начать!
EDIT2 (более 1 года спустя, сейчас)
есть еще
set()
. Это может быть быстрее, если есть много столбцов зацикливается, как он избегает (небольшие) накладные расходы на вызов[,:=,]
в цикле.set
является loopable:=
. Смотрите?set
.f_dowle3 = function(DT) { # either of the following for loops # by name : for (j in names(DT)) set(DT,which(is.na(DT[[j]])),j,0) # or by number (slightly faster than by name) : for (j in seq_len(ncol(DT))) set(DT,which(is.na(DT[[j]])),j,0) }
вот самый простой, который я мог бы придумать:
dt[is.na(dt)] <- 0
это эффективно и не нужно писать функции и другой код клея.
вот решение с помощью
NAToUnknown
наgdata
пакета. Я использовал решение Андри для создания огромной таблицы данных, а также включил сравнение времени с решением Андри.# CREATE DATA TABLE dt1 = create_dt(2e5, 200, 0.1) # FUNCTIONS TO SET NA TO ZERO f_gdata = function(dt, un = 0) gdata::NAToUnknown(dt, un) f_Andrie = function(dt) remove_na(dt) # COMPARE SOLUTIONS AND TIMES system.time(a_gdata <- f_gdata(dt1)) user system elapsed 4.224 2.962 7.388 system.time(a_andrie <- f_Andrie(dt1)) user system elapsed 4.635 4.730 20.060 identical(a_gdata, g_andrie) TRUE
library(data.table) DT = data.table(a=c(1,"A",NA),b=c(4,NA,"B")) DT a b 1: 1 4 2: A NA 3: NA B DT[,lapply(.SD,function(x){ifelse(is.na(x),0,x)})] a b 1: 1 4 2: A 0 3: 0 B
просто для справки, медленнее по сравнению с gdata или данными.матрица, но использует только данные.пакет таблицы и может общаться с не численными входами.
для полноты картины, другой способ заменить NAs на 0-это использовать
f_rep <- function(dt) { dt[is.na(dt)] <- 0 return(dt) }
для сравнения результатов и времени я включил все подходы, упомянутые до сих пор.
set.seed(1) dt1 <- create_dt(2e5, 200, 0.1) dt2 <- dt1 dt3 <- dt1 system.time(res1 <- f_gdata(dt1)) User System verstrichen 3.62 0.22 3.84 system.time(res2 <- f_andrie(dt1)) User System verstrichen 2.95 0.33 3.28 system.time(f_dowle2(dt2)) User System verstrichen 0.78 0.00 0.78 system.time(f_dowle3(dt3)) User System verstrichen 0.17 0.00 0.17 system.time(res3 <- f_unknown(dt1)) User System verstrichen 6.71 0.84 7.55 system.time(res4 <- f_rep(dt1)) User System verstrichen 0.32 0.00 0.32 identical(res1, res2) & identical(res2, res3) & identical(res3, res4) & identical(res4, dt2) & identical(dt2, dt3) [1] TRUE
таким образом, новый подход немного медленнее, чем
f_dowle3
но быстрее, чем все другие подходы. Но, честно говоря, это противоречит моей интуиции данных.синтаксис таблицы, и я понятия не имею, почему это работает. Может ли кто-нибудь просветить меня?
Я понимаю, что секрет быстрых операций в R заключается в использовании вектора (или массивов, которые являются векторами под капотом.)
в этом решении я использую
data.matrix
что этоarray
но ведите себя немного какdata.frame
. Поскольку это массив, вы можете использовать очень простую векторную подстановку для заменыNA
s:небольшая вспомогательная функция для удаления
NA
s. суть заключается в одной строке кода. Я делаю это только для измерения выполнения время.remove_na <- function(x){ dm <- data.matrix(x) dm[is.na(dm)] <- 0 data.table(dm) }
небольшая вспомогательная функция для создания
data.table
заданного размера.create_dt <- function(nrow=5, ncol=5, propNA = 0.5){ v <- runif(nrow * ncol) v[sample(seq_len(nrow*ncol), propNA * nrow*ncol)] <- NA data.table(matrix(v, ncol=ncol)) }
демонстрация на крошечном образце:
library(data.table) set.seed(1) dt <- create_dt(5, 5, 0.5) dt V1 V2 V3 V4 V5 [1,] NA 0.8983897 NA 0.4976992 0.9347052 [2,] 0.3721239 0.9446753 NA 0.7176185 0.2121425 [3,] 0.5728534 NA 0.6870228 0.9919061 NA [4,] NA NA NA NA 0.1255551 [5,] 0.2016819 NA 0.7698414 NA NA remove_na(dt) V1 V2 V3 V4 V5 [1,] 0.0000000 0.8983897 0.0000000 0.4976992 0.9347052 [2,] 0.3721239 0.9446753 0.0000000 0.7176185 0.2121425 [3,] 0.5728534 0.0000000 0.6870228 0.9919061 0.0000000 [4,] 0.0000000 0.0000000 0.0000000 0.0000000 0.1255551 [5,] 0.2016819 0.0000000 0.7698414 0.0000000 0.0000000