danfo.js でエクセルデータを扱う場合に、空欄のセルがあるとうまくいかない場合があるようです。単純に読み込みに失敗してくれれば、ある意味問題ないのですが、 読み込み失敗はしないが データが意図通り読み込めていない という場合、 特にエクセルファイルが巨大だったりして、そのまま気付かず進行して後行程で事故るという恐怖。 本当に意図通り読み込みできているか、注意しましょう。
そこで、読み込みには SheetJS を使用し、データ処理自体は danfo.js を使うという方法を使います。 danfo.js のモジュールの中身を見てみると、内部では SheetJS(xlsx) を使用しているみたいなのですが...
このファイル(maclist-without-empty.xlsx)は問題なく読み込みできました。
プロジェクトディレクトリを作成して、セットアップします。
$ mkdir danfo-and-xlsx
$ cd danfo-and-xlsx
$ npm init -y
$ npm install danfojs-node
$ touch index.js
maclist-without-empty.xlsx を読み込むコードを用意。
// index.js
const DFD = require('danfojs-node');
const proc = async ()=>{
const df = await DFD.readExcel('maclist-without-empty.xlsx');
df.print();
};
proc();
実行すると次のようになります。
$ node index.js
╔════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ kind │ name │ price ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ laptop │ MacBook Air │ 98000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ laptop │ MacBook Pro │ 248000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ desktop │ Mac mini │ 78000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ desktop │ iMac │ 154800 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 4 │ desktop │ MacPro │ 715000 ║
╚════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
なお、テストに使用している環境は以下の通り。
$ node --version
v18.12.0
hello-danfo $ npm -version
8.19.2
このファイル(maclist-with-empty.xlsx)は、うまく読み込みできませんでした。
index.js のファイル読み込み部分を書き換えます。
//const df = await DFD.readExcel('maclist-without-empty.xlsx');
const df = await DFD.readExcel('maclist-with-empty.xlsx');
実行します。
$ node index.js
throw new Error('Table must have a consistent number of cells.');
maclist-without-empty.xlsx と maclist-with-empty.xlsx の違いは、 A2 セルが空でないか、空かの違いしかありませんから、この空のセルの問題でしょう。
index.js を xlsx を使ってデータを読み込むように修正します。
const XLSX = require('xlsx')
const DFD = require('danfojs-node');
const workbook = XLSX.readFile('maclist-with-empty.xlsx');
const sheetNames = workbook.SheetNames;
const worksheet = workbook.Sheets[sheetNames[0]];
//
// 6行x3列の範囲を読み込む.
//
const rowCount = 6
const colCount = 3
const rowRange = [...Array(rowCount).keys()];
const colRange = [...Array(colCount).keys()];
colRange.forEach( (colIndex)=> {
rowRange.forEach( (rowIndex)=> {
const cellAddress = XLSX.utils.encode_cell( {c:colIndex, r:rowIndex} );
const cell = worksheet[cellAddress];
console.log(`${cellAddress} = ${cell}`);
});
});
SheetJS を使うため xlsx モジュールを入れます。
$ npm install xlsx
実行:
$ node index.js
A1 = [object Object]
A2 = [object Object]
A3 = [object Object]
A4 = undefined
A5 = [object Object]
A6 = [object Object]
B1 = [object Object]
B2 = [object Object]
B3 = [object Object]
B4 = [object Object]
B5 = [object Object]
B6 = [object Object]
C1 = [object Object]
C2 = [object Object]
C3 = [object Object]
C4 = [object Object]
C5 = [object Object]
C6 = [object Object]
A4 セルだけ(空なので) undefined になっています。
では、このコードを修正して 読み込んだ結果を DataFrame に変換します。
const XLSX = require('xlsx')
const DFD = require('danfojs-node');
const workbook = XLSX.readFile('maclist-with-empty.xlsx');
const sheetNames = workbook.SheetNames;
const worksheet = workbook.Sheets[sheetNames[0]];
//
// 6行x3列の範囲を読み込む.
//
const rowCount = 6
const colCount = 3
const rowRange = [...Array(rowCount).keys()];
const colRange = [...Array(colCount).keys()];
const cellList = colRange.map( (colIndex)=> {
return rowRange.map( (rowIndex)=> {
const cellAddress = XLSX.utils.encode_cell( {c:colIndex, r:rowIndex} );
const cell = worksheet[cellAddress];
//console.log(`${cellAddress} = ${cell}`);
if( cell && cell.w ){
return {
text: cell.w,
colIndex: colIndex,
rowIndex: rowIndex};
}
else {
// 値が空のセルは空文字列を割り当てます.
return {
text: '',
colIndex: colIndex,
rowIndex: rowIndex};
}
});
}).flat();
const head = (list)=>{ return list[0]; }
const tail = (list)=>{ return list.slice(1) };
const column0Values = cellList.filter((cell)=> cell.colIndex==0).map((cell)=> cell.text);
const column1Values = cellList.filter((cell)=> cell.colIndex==1).map((cell)=> cell.text);
const column2Values = cellList.filter((cell)=> cell.colIndex==2).map((cell)=> cell.text);
const tableObject = {};
const df = new DFD.DataFrame({
[head(column0Values)]: tail(column0Values),
[head(column1Values)]: tail(column1Values),
[head(column2Values)]: tail(column2Values)
});
df.print()
実行:
$ node index.js
╔════════════╤═══════════════════╤═══════════════════╤═══════════════════╗
║ │ kind │ name │ price ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 0 │ laptop │ MacBook Air │ 98000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 1 │ laptop │ MacBook Pro │ 248000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 2 │ │ Mac mini │ 78000 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 3 │ desktop │ iMac │ 154800 ║
╟────────────┼───────────────────┼───────────────────┼───────────────────╢
║ 4 │ desktop │ MacPro │ 715000 ║
╚════════════╧═══════════════════╧═══════════════════╧═══════════════════╝
できました。