pandas の代わりに使える danfo.js というツールがあることを知る。 pandas は Pythonのツールなので、JavaScript の方が慣れている人にとって danfo.js はありがたい。

こんなエクセルデータがあったとして、これを danfo.js で操作していく覚え書き。
$ node --version
v16.15.0
$ npm -version
8.5.5
$ mkdir hello-danfo
$ cd hello-danfo
$ npm init -y
$ npm install danfojs-node
pandas 互換になっているらしいので、さまざまな操作ができると思う。 ここでは、以前のエントリーで pandas を使って特定の列を抜き出し新しいエクセルデータを作り出す操作をやってみる。
まずは、冒頭のエクセルのファイル ./maclist.xlsx を読み込み:
//
// index.js
//
const dfd = require('danfojs-node');
const proc = async ()=>{
	const df = await dfd.readExcel('maclist.xlsx');
	df.print();
};
proc();
実行:
$ node index.js
╔════════════╤═══════════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║            │ id                │ kind              │ name              │ price             ║
╟────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0          │ mba               │ laptop            │ MacBook Air       │ 98000             ║
╟────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1          │ mbp               │ laptop            │ MacBook Pro       │ 248000            ║
╟────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2          │ mm                │ desktop           │ Mac mini          │ 78000             ║
╟────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 3          │ im                │ desktop           │ iMac              │ 154800            ║
╟────────────┼───────────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 4          │ mp                │ desktop           │ MacPro            │ 715000            ║
╚════════════╧═══════════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
できた!(データを読み込めただけだが)
それでは本題の、name と price 列だけを取り出した DataFrame を新規に作成して、それをエクセルデータとして保存をやってみる。
まず、既存の df から name 列を取り出してみよう.
console.log(df['name']);
実行:
$ node index.js
Series {
  '$dataIncolumnFormat': [ 'MacBook Air', 'MacBook Pro', 'Mac mini', 'iMac', 'MacPro' ],
  '$index': [ 0, 1, 2, 3, 4 ],
  '$columns': [ 'name' ],
  '$dtypes': [ 'string' ],
  '$isSeries': true,
  '$config': Configs {
    tableDisplayConfig: {},
    tableMaxRow: 10,
    tableMaxColInConsole: 10,
    dtypeTestLim: 20,
    lowMemoryMode: false
  },
  '$data': [ 'MacBook Air', 'MacBook Pro', 'Mac mini', 'iMac', 'MacPro' ]
}
df['カラム名'] すると Series オブジェクトが返ってくる。
ならば df['name'].values すれば、その列の配列が返るであろう。
console.log(df['name'].values);
実行:
$ node index.js
[ 'MacBook Air', 'MacBook Pro', 'Mac mini', 'iMac', 'MacPro' ]
意図通り、name 列の値が配列で取得できました。
新規のデータフレームを作成して、 初期値として name2 列を入れてみます。
const df2 = new dfd.DataFrame([df['name'].values], {columns: ['name2']});
df2.print();
実行してみると:
$ node index.js
Error: ParamError: Column names length mismatch. You provided a column of length 1 but Ndframe columns has length of 5
エラー。列数は1 を指定されているがデータは5列分ある、というエラーメッセージ。 つまり、[df['name'].values] を転置して与える必要があるようだ。
ならば、underscore を使って転置したデータを使って、再トライ!
$ npm install underscore
該当箇所を修正します。 転置(行と列の入れ替え)は transpose すればOKです。
const df2 = new dfd.DataFrame(_.transpose([df['name'].values]), {columns: ['name2']});
コード全体は:
//
// index.js
//
const dfd = require('danfojs-node')
const _ = require('underscore')
const proc = async ()=>{
    const df = await dfd.readExcel('maclist.xlsx');
    console.log(df['name'].values);
    const df2 = new dfd.DataFrame(_.transpose([df['name'].values]), {columns: ['name2']});
    df2.print();
};
proc();
実行:
$ node index.js
[ 'MacBook Air', 'MacBook Pro', 'Mac mini', 'iMac', 'MacPro' ]
╔════════════╤═══════════════════╗
║            │ name2             ║
╟────────────┼───────────────────╢
║ 0          │ MacBook Air       ║
╟────────────┼───────────────────╢
║ 1          │ MacBook Pro       ║
╟────────────┼───────────────────╢
║ 2          │ Mac mini          ║
╟────────────┼───────────────────╢
║ 3          │ iMac              ║
╟────────────┼───────────────────╢
║ 4          │ MacPro            ║
╚════════════╧═══════════════════╝
今度はうまくいきました。
それではここに price 列を追加してみましょう。
df2.addColumn('price2', df['price'].values, {inplace: true});
df2.print();
実行:
$ node index.js
╔════════════╤═══════════════════╤═══════════════════╗
║            │ name2             │ price2            ║
╟────────────┼───────────────────┼───────────────────╢
║ 0          │ MacBook Air       │ 98000             ║
╟────────────┼───────────────────┼───────────────────╢
║ 1          │ MacBook Pro       │ 248000            ║
╟────────────┼───────────────────┼───────────────────╢
║ 2          │ Mac mini          │ 78000             ║
╟────────────┼───────────────────┼───────────────────╢
║ 3          │ iMac              │ 154800            ║
╟────────────┼───────────────────┼───────────────────╢
║ 4          │ MacPro            │ 715000            ║
╚════════════╧═══════════════════╧═══════════════════╝
これで、既存のエクセルから name, price 列を取り出した新しいデータフレームを生成できました。
最後に、name2 と price2 列の情報をマージした新しい text 列を追加してみます。
const textValues = _.map(_.transpose([df2['name2'].values, df2['price2'].values]), (row)=>{
    return `${row[0]} : ${row[1]}yen`
});
df2.addColumn('text', textValues, {inplace: true});
df2.print();
実行:
$ node index.js
╔════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║            │ name2             │ price2            │ text              ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0          │ MacBook Air       │ 98000             │ MacBook Air : 9…  ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1          │ MacBook Pro       │ 248000            │ MacBook Pro : 2…  ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2          │ Mac mini          │ 78000             │ Mac mini : 7800…  ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 3          │ iMac              │ 154800            │ iMac : 154800yen  ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 4          │ MacPro            │ 715000            │ MacPro : 715000…  ║
╚════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
できました!
あとはこれをCSVとして保存:
dfd.toCSV(df2, {filePath: 'result.csv'});
実行して、 result.csv を確認:
$ cat result.csv 
name2,price2,text
MacBook Air,98000,MacBook Air : 98000yen
MacBook Pro,248000,MacBook Pro : 248000yen
Mac mini,78000,Mac mini : 78000yen
iMac,154800,iMac : 154800yen
MacPro,715000,MacPro : 715000yen
問題ありません。
最後にここまで書いたコード全体を掲載します。
//
// index.js
//
const dfd = require('danfojs-node')
const _ = require('underscore')
const proc = async ()=>{
    const df = await dfd.readExcel('maclist.xlsx');
    //console.log(df['name'].values);
    const df2 = new dfd.DataFrame(_.transpose([df['name'].values]), {columns: ['name2']});
    df2.addColumn('price2', df['price'].values, {inplace: true});
    const textValues = _.map(_.transpose([df2['name2'].values, df2['price2'].values]), (row)=>{
        return `${row[0]} : ${row[1]}yen`
    });
    df2.addColumn('text', textValues, {inplace: true});
    df2.print();
    dfd.toCSV(df2, {filePath: 'result.csv'});
};
proc();
以上です。
danfo.js 簡単に扱えました。 公式ドキュメントが充実していて助かります。
pandasで同じことをしている以前のエントリー と同じ手順で、元になるエクセルデータから name, price 列を抜き出して新しいデータフレームを生成するコード。
//
// index.js
//
const dfd = require('danfojs-node')
const proc = async ()=>{
    const df = await dfd.readExcel('maclist.xlsx');
    const nameValues = df['name'].values;
    const priceValues = df['price'].values;
    const df2 = new dfd.DataFrame( {name: nameValues, price: priceValues} )
    df2.print();
};
proc();
こちらのほうが簡単に記述できました。