この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
Introduction
例えば機械学習のトレーニングデータを用意するとき、
csvからデータを読み込んだり列や行を追加・削除・変更したりグルーピングしたりなどなど、
データの加工がほぼ必須です。
そういったときにデータ解析用ライブラリとしてPythonのPandasなどがよく使われます。
とても使いやすいライブラリなのですが、
Pythonに不慣れな私としてはJavascriptでなんとかしたいなと思っていました。
そこで探してみたところ、みつけたのがDanfo.jsです。
Environment
- OS : MacOS 10.15.7
- Node : v14.16.1
Danfo.js?
Danfo.jsとは、PythonのデータフレームライブラリであるPandasにインスパイアされた、
構造化データの操作や処理を実施するためのJavascriptライブラリです。
(おそらく)Pandasを使える人ならすぐに馴染めると思われます。
csvやjsonファイル、TensorflowのオブジェクトからDataFrameを構築したり、
それらのDataFrameに対して変更や結合、データのグルーピングなどいろいろな操作も提供しています。
Setup
npmでインストールします。
% npm install danfojs-node
(2021/8月時点)
danfojsはtensorflow.jsに依存しているようで、M1 Macでは動きませんでした。
また、tensor 2dをdanfojsにそのまま渡すことができるみたいでしたが、
自分の環境ではエラーだった(多分バージョンとかそのへんの問題)のでそこは未確認です。
Use Danfojs
ではつかってみましょう。
まずは配列データからDataFrameを作成して表示してみます。
(printでコンソール表示)
//danfojs-main.js
const dfd = require("danfojs-node");
const arr_data = [[1, 'hello', 3.2],
[5, 'danfojs',7.5],[3, 'javascipt',10.0]];
df = new dfd.DataFrame(arr_data)
df.print()
% node danfojs-main.js
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ 0 │ 1 │ 2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 1 │ hello │ 3.2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 3 │ javascipt │ 10 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
ctypesで型情報がわかる。
df.ctypes.print()
╔═══╤══════════════════════╗
║ │ 0 ║
╟───┼──────────────────────╢
║ 0 │ int32 ║
╟───┼──────────────────────╢
║ 1 │ string ║
╟───┼──────────────────────╢
║ 2 │ float32 ║
╚═══╧══════════════════════╝
headで頭から、tailで後ろから任意の行数を取得。
df.head(1).print()
df.tail(1).print()
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ 0 │ 1 │ 2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 1 │ hello │ 3.2 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ 0 │ 1 │ 2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 3 │ javascipt │ 10 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
column指定も可能。
describeでサマリ情報取得。
const arr_data = [[1, 'hello', 3.2],
[5, 'danfojs',7.5],[3, 'javascipt',10.0]];
df = new dfd.DataFrame(arr_data,{columns: ["id", "name", "number"]});
console.log(df.index);
console.log(df.columns);
df.describe().print()
[ 0, 1, 2 ]
[ 'id', 'u_name', 'number' ]
╔══════════╤═══════════════════╤═══════════════════╗
║ │ id │ number ║
╟──────────┼───────────────────┼───────────────────╢
║ count │ 3 │ 3 ║
╟──────────┼───────────────────┼───────────────────╢
║ mean │ 3 │ 6.9 ║
╟──────────┼───────────────────┼───────────────────╢
║ std │ 2 │ 3.439477 ║
╟──────────┼───────────────────┼───────────────────╢
║ min │ 1 │ 3.2 ║
╟──────────┼───────────────────┼───────────────────╢
║ median │ 3 │ 7.5 ║
╟──────────┼───────────────────┼───────────────────╢
║ max │ 5 │ 10 ║
╟──────────┼───────────────────┼───────────────────╢
║ variance │ 4 │ 11.83 ║
╚══════════╧═══════════════════╧═══════════════════╝
sort可能。
df.sort_values({by: "number", ascending:false,inplace: true});
df.print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ u_name │ number ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 3 │ javascipt │ 10 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 1 │ hello │ 3.2 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
pandasでも使えるlocとかilocとか。
slice指定もできます。
console.log("loc")
df.loc({rows: [0,1]}).print()
console.log("iloc")
df.iloc({rows: [1,2]}).print()
console.log("slice")
df.iloc({rows: ["0:3"]}).print()
loc
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ u_name │ number ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 1 │ hello │ 3.2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
iloc
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ u_name │ number ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 3 │ javascipt │ 10 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
slice
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ u_name │ number ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 1 │ hello │ 3.2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 3 │ javascipt │ 10 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
queryを使うとSQLのwhereみたいなかんじで行を抽出できます。
console.log("query-1")
df.query({ "column": "number", "is": ">", "to": 5 }).print();
console.log("query-2")
query_df = df.query({ column: "u_name", is: "==", to: "danfojs"}).print();
query-1
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ u_name │ number ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 3 │ javascipt │ 10 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
query-2
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ u_name │ number ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 5 │ danfojs │ 7.5 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
dropnaを使えば不正データを除去できる。
const json_data = [
{ A:1,B:'hello',C:NaN},
{ A:5, B:'danfojs',C:7.5 },
{ A:3, B:'javascipt',C:10.0 },
{ A:NaN,B:null,C:3.3}];
df = new dfd.DataFrame(json_data);
df.isna().print();
df.dropna({axis: 0}).print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ A │ B │ C ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ false │ false │ true ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ false │ false │ false ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ false │ false │ false ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ true │ true │ false ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ A │ B │ C ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 5 │ danfojs │ 7.5 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 3 │ javascipt │ 10 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
applyをつかって各値に関数を適用できます。
const data = [[1, 2, 3], [4, 5, 6], [20, 30, 40], [39, 89, 78]]
const cols = ["A", "B", "C"]
const df = new dfd.DataFrame(data, { columns: cols })
function square(x) {
return x ** 2
}
df.apply({callable: square }).print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ A │ B │ C ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ 1 │ 4 │ 9 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ 16 │ 25 │ 36 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ 400 │ 900 │ 1600 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ 1521 │ 7921 │ 6084 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
concatでDataFrame同士を結合できます。
let data_1 = [['U1', 'taro'], ['U2', 'hanako'],['U3','takeshi']];
let data_2 = [['tokyo', 20],['saitama',30], ['okinawa',40]];
let colum1 = ['id', 'name'];
let colum2 = ['address', 'age'];
let df_1 = new dfd.DataFrame(data_1, { columns: colum1 });
let df_2 = new dfd.DataFrame(data_2, { columns: colum2 });
dfd.concat({ df_list: [df_1, df_2], axis: 1 }).print();
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ id │ name │ address │ age ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ U1 │ taro │ tokyo │ 20 ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ U2 │ hanako │ saitama │ 30 ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ U3 │ takeshi │ okinawa │ 40 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
Groupingもいろいろとできます。
let data_g ={'A': ['foo', 'bar', 'foo', 'bar',
'foo', 'bar', 'foo', 'foo'],
'B': ['one', 'one', 'two', 'three',
'two', 'two', 'one', 'three'],
'C': [1,3,2,4,5,2,6,7],
'D': [3,2,4,1,5,6,7,8]
}
let df_g = new dfd.DataFrame(data_g);
let grp = df_g.groupby(["A"]);
grp.get_groups(["foo"]).print();
grp.col(["C"]).sum().print();
let grp_2 = df_g.groupby(["A","B"])
grp_2.col(["C"]).sum().print()
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ A │ B │ C │ D ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ foo │ one │ 1 │ 3 ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ foo │ two │ 2 │ 4 ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ foo │ two │ 5 │ 5 ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ foo │ one │ 6 │ 7 ║
╟───┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 4 │ foo │ three │ 7 │ 8 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
╔═══╤═══════════════════╤═══════════════════╗
║ │ A │ C_sum ║
╟───┼───────────────────┼───────────────────╢
║ 0 │ foo │ 21 ║
╟───┼───────────────────┼───────────────────╢
║ 1 │ bar │ 9 ║
╚═══╧═══════════════════╧═══════════════════╝
╔═══╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ A │ B │ C_sum ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ foo │ one │ 7 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ foo │ two │ 7 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ foo │ three │ 7 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ bar │ one │ 3 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 4 │ bar │ two │ 2 ║
╟───┼───────────────────┼───────────────────┼───────────────────╢
║ 5 │ bar │ three │ 4 ║
╚═══╧═══════════════════╧═══════════════════╧═══════════════════╝
csvファイルを読み込むこともかんたんにできます。
ためしにこの記事でつかったペンギンデータ(penguins_size.csv)を
読んでみます。
const dfd = require("danfojs-node")
async function exec() {
let df = await dfd.read("path/your/penguins_size.csv");
console.log(df.shape);
let df_schema = df
.astype({column: "culmen_length_mm", dtype: "float32"})
.astype({column: "flipper_length_mm", dtype: "float32"})
.astype({column: "body_mass_g", dtype: "int32"})
.astype({column: "culmen_depth_mm", dtype: "float32"});
//replace NA to null
let df_rep = df_schema.replace({ "replace": "NA", "with": null, "in": ["sex"] });
let df_new2 = df_rep.dropna({axis: 0})
df_new2.ctypes.print()
df_new2.print();
}
exec();
[ 344, 7 ]
╔═══════════════════╤══════════════════════╗
║ │ 0 ║
╟───────────────────┼──────────────────────╢
║ species │ string ║
╟───────────────────┼──────────────────────╢
║ island │ string ║
╟───────────────────┼──────────────────────╢
║ culmen_length_mm │ float32 ║
╟───────────────────┼──────────────────────╢
║ culmen_depth_mm │ float32 ║
╟───────────────────┼──────────────────────╢
║ flipper_length_mm │ int32 ║
╟───────────────────┼──────────────────────╢
║ body_mass_g │ int32 ║
╟───────────────────┼──────────────────────╢
║ sex │ string ║
╚═══════════════════╧══════════════════════╝
╔════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ species │ island │ culmen_length_mm │ culmen_depth_mm │ flipper_lengt... │ body_mass_g │ sex ║
╟────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ Adelie │ Torgersen │ 39.1 │ 18.7 │ 181 │ 3750 │ MALE ║
╟────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ Adelie │ Torgersen │ 39.5 │ 17.4 │ 186 │ 3800 │ FEMALE ║
╟────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
・・・・・・・・・
Summary
今回はJavascript用DataFrameライブラリ、Danfo.jsを使ってみました。
DataFrameのファイル出力(csvやjson)もかんたんにできますし、
データ整形が楽になりそうです。