RのDTパッケージを使うとJavaScriptのDataTablesライブラリ(DataTables | Table plug-in for jQuery)利用したインタラクティブなテーブルが作成できる。
しかし、JavaScriptのライブラリである都合上、Rでは可能なことが簡単には実現できないということもある。表題に挙げたNAやInfの扱いもその一つ。
NAやInfはnullになる
まず、適当にNAやInfを含むデータフレームを作成する。
data <- tibble::tibble( x = c(1, 12, 112, NA, Inf, -Inf), y = c(1, 12, 112, NA, NA, NA), z = c(1, 12, 112, -999, 999, NA) ) data ## # A tibble: 6 × 3 ## x y z ## <dbl> <dbl> <dbl> ## 1 1 1 1 ## 2 12 12 12 ## 3 112 112 112 ## 4 NA NA -999 ## 5 Inf NA 999 ## 6 -Inf NA NA
これをDT::datatable()で表示する。
library(DT) datatable(data, filter = "top")

このようにNAもInfも表示されない。これはデータが一旦JSONに変換されるが、JSONではNaNやInfinityを表現できないのでnullで置換されて空で表示されるということらしい。なので当然だがソート時も正しく扱われない。
ただ、列xのようにInfを含むとcolumn filterが効かなくなるので、単純にnullだけかというとそうでもなさそうに見える。なお、後の例示でも出てくるのでfilter = "top"を指定してcolumn filterを表示したが、これはDTパッケージの側で追加されている機能となっている(DataTablesでも同様のものを表示できるが、オプション1つでON/OFFできるレベルではなく、ある程度の実装が必要)。
NAやInfを表示したり、ソートで扱えるようにする方法はいくつか考えられる。ただ、いずれも一長一短という感じで、完全に問題がない方法は思いついていない。
DT.TOJSON_ARGSオプションを使う
このオプションで以下のように指定すると、NAやInfが表示されるようになる。
options(DT.TOJSON_ARGS = list(na = "string"))

この方法には大きな欠点があり、上記のようにソートが文字列によるソートとなってしまう(上記スクリーンショットはx列でソートしている)。
ただ、Shinyを使ってサーバーサイド処理を行う場合はソートがRで行われるため、期待するようなソートになるらしい。これは試していないが、そうであれば特に欠点が無い方法のように思う。
na = NULLで元の挙動に戻る。
options(DT.TOJSON_ARGS = list(na = NULL))
欠損値をすべてNAやInfinityに置き換える
やや乱暴ではあるが、出現する欠損値が1種類であれば、ある程度妥当な挙動となる。
欠損値を置き換えるには、options引数の中で次のようにrenderにJavaScriptの関数を指定する。
datatable( data, filter = "top", options = list( columnDefs = list( list( targets = "_all", # 設定を適用するカラム。個別指定は数値で、"_all"はすべての列への指定となる。 type = "num", # この指定が無いと文字列でのソートになる。 render = JS( ' function(data, type, row, meta) { return data === null ? Infinity : data; } ' ) ) ) ) )

この場合、ソートも期待通りに動く。上記スクリーンショットではz列をソートしている。
なお、元データがInfを含むとcolumn filterが動かないというのは変わらないので、この方法をとる場合はdatatable()に渡す前にInfをNAに置換しておいたほうが良いだろう。
DT.TOJSON_ARGSとrenderでの置き換えを組み合わせる
DT.TOJSON_ARGSでna = "string"を指定するとNAやInfが表示されるが、JavaScriptでは解釈できないのでクライアントサイドの処理では文字列ソートになってしまうというのが課題だった。つまり、NaNやInfinityなどのJavaScriptで解釈可能な形式に置き換えてやれば良い。
options(DT.TOJSON_ARGS = list(na = "string")) datatable( data, filter = "top", options = list( columnDefs = list( list( targets = "_all", type = "num", render = JS( ' function(data, type, row, meta) { return data === "NA" ? NaN : data === "Inf" ? Infinity : data === "-Inf" ? -Infinity : data; } ' ) ) ) ) )

この方法は概ね上手く動く。ただ、次のような欠点はある。
Infを含むとcolumn filterが動作しない問題は解決しない。DT.TOJSON_ARGSの設定はすべての列に効いてしまうので、NA等を表示したくない場合にも表示される。- 表示したくない列用に
renderを別途記述すれば良いが、多少手間がかかる。
- 表示したくない列用に
-InfinityとNaNの間のソート順序が気持ち悪い。- JavaScriptの仕様と思われるが、
(a, b) => b - aのような比較関数を書いてコンソールで試した場合の結果とも異なるので良くわからない。
- JavaScriptの仕様と思われるが、
Infではなく特定の値をInfinityに置き換える
datatable( data, filter = "top", options = list( columnDefs = list( list( targets = "_all", type = "num", render = JS( ' function(data, type, row, meta) { return data === null ? NaN : data === 999 ? Infinity : data === -999 ? -Infinity : data; } ' ) ) ) ) )

先程の方法とほぼ同じだが、column filterが動作する。ただ、値の上限と下限は値を置き換える前の状態に依存してしまう。

置き換える前の値をデータ範囲に応じて違和感の無い値(例えば最大値+1など)に処理しておく手間をかけられるなら良いかもしれない。