web-dev-qa-db-ja.com

リスト内のNULLをNAに置き換えるより効率的な方法はありますか?

私は次のような構造のデータに出くわすことがよくあります。

employees <- list(
    list(id = 1,
             dept = "IT",
             age = 29,
             sportsteam = "softball"),
    list(id = 2,
             dept = "IT",
             age = 30,
             sportsteam = NULL),
    list(id = 3,
             dept = "IT",
             age = 29,
             sportsteam = "hockey"),
    list(id = 4,
             dept = NULL,
             age = 29,
             sportsteam = "softball"))

多くの場合、このようなリストの長さは数千万アイテムになる可能性があるため、メモリの問題と効率が常に問題になります。

リストをデータフレームに変換したいのですが、実行すると:

library(data.table)
employee.df <- rbindlist(employees)

NULL値が原因でエラーが発生します。私の通常の戦略は、次のような関数を使用することです。

nullToNA <- function(x) {
    x[sapply(x, is.null)] <- NA
    return(x)
}

その後:

employees <- lapply(employees, nullToNA)
employee.df <- rbindlist(employees)

返す

   id dept age sportsteam
1:  1   IT  29   softball
2:  2   IT  30         NA
3:  3   IT  29     hockey
4:  4   NA  29   softball

ただし、nullToNA関数は1,000万件に適用すると非常に遅くなるため、より効率的なアプローチがあればよいでしょう。

Is.null関数は一度に1つのアイテムにしか適用できません(1回で完全なリストをスキャンできるis.naとは異なります)。

大規模なデータセットでこの操作を効率的に行う方法に関するアドバイスはありますか?

32
Jon M

Rの多くの効率性の問題は、最初に元のデータを、可能な限り高速で簡単なプロセスを実行できる形式に変更することで解決されます。通常、これは行列形式です。

すべてのデータをrbindでまとめると、nullToNA関数はネストされたリストを検索する必要がなくなるため、sapplyはその目的を果たします(マトリックスを通して)。効率的に。理論的には、これによりプロセスが高速になります。

いい質問ですね。

> dat <- do.call(rbind, lapply(employees, rbind))
> dat
     id dept age sportsteam
[1,] 1  "IT" 29  "softball"
[2,] 2  "IT" 30  NULL      
[3,] 3  "IT" 29  "hockey"  
[4,] 4  NULL 29  "softball"

> nullToNA(dat)
     id dept age sportsteam
[1,] 1  "IT" 29  "softball"
[2,] 2  "IT" 30  NA        
[3,] 3  "IT" 29  "hockey"  
[4,] 4  NA   29  "softball"
16
Rich Scriven

2ステップのアプローチでは、rbindと組み合わせてデータフレームを作成します。

employee.df<-data.frame(do.call("rbind",employees))

ここでNULLを置き換えます。データをロードするときにRがNULLを入れず、ロードするときに文字として読み取るため、「NULL」を使用しています。

employee.df.withNA <- sapply(employee.df, function(x) ifelse(x == "NULL", NA, x))
5
infominer

私が読みやすいと思う整然とした解決策は、単一の要素で機能する関数を記述し、それをすべてのNULLにマップすることです。

@ rich-scrivenのrbind and lapplyアプローチを使用してマトリックスを作成し、それをデータフレームに変換します。

_library(magrittr)

dat <- do.call(rbind, lapply(employees, rbind)) %>% 
  as.data.frame()

dat
#>   id dept age sportsteam
#> 1  1   IT  29   softball
#> 2  2   IT  30       NULL
#> 3  3   IT  29     hockey
#> 4  4 NULL  29   softball
_

次に、深さ2でpurrr::modify_depth()を使用してreplace_x()を適用できます。

_replace_x <- function(x, replacement = NA_character_) {
  if (length(x) == 0 || length(x[[1]]) == 0) {
    replacement
  } else {
    x
  }
}

out <- dat %>% 
  purrr::modify_depth(2, replace_x)

out
#>   id dept age sportsteam
#> 1  1   IT  29   softball
#> 2  2   IT  30         NA
#> 3  3   IT  29     hockey
#> 4  4   NA  29   softball
_
2
amanda

これらのすべてのソリューション(私は思う)は、データテーブルがまだベクターのリストではなくリストの損失であるという事実を隠しています(_:=_の間に予期しないエラーをスローし始めるまで、アプリケーションで気付きませんでした) 。これを試して:

data.table(t(sapply(employees, function(x) unlist(lapply(x, function(x) ifelse(is.null(x),NA,x))))))

私はそれがうまくいくと信じていますが、それが遅いことに苦しみ、さらに最適化できるかどうかはわかりません。

1

do.call()関数は読みにくいと感じることがよくあります。私が毎日使用するソリューション("NULL"文字値を含むMySQL出力を使用):

NULL2NA <- function(df) {
  df[, 1:length(df)][df[, 1:length(df)] == 'NULL'] <- NA
  return(df)
}

しかし、すべての解決策について:NAna.rm = TRUEなしでは計算に使用できませんが、NULLでは使用できることを覚えておいてください。 NaNも同じ問題を引き起こします。例えば:

> mean(c(1, 2, 3))
2

> mean(c(1, 2, NA, 3))
NA

> mean(c(1, 2, NULL, 3))
2

> mean(c(1, 2, NaN, 3))
NaN
1
MS Berends