Home About Contact
danfo.js , pandas , Excel , JavaScript

danfo.js, pandas の JavaScript版

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

MacList

こんなエクセルデータがあったとして、これを 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 列を取り出した新しいデータフレームを生成できました。

最後に、name2price2 列の情報をマージした新しい 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();

こちらのほうが簡単に記述できました。