Home About
danfo.js , sheetjs , Excel , JavaScript

danfo.js 空のセルが存在するエクセルファイルを読み込み失敗するなど

danfo.js でエクセルデータを扱う場合に、空欄のセルがあるとうまくいかない場合があるようです。単純に読み込みに失敗してくれれば、ある意味問題ないのですが、 読み込み失敗はしないが データが意図通り読み込めていない という場合、 特にエクセルファイルが巨大だったりして、そのまま気付かず進行して後行程で事故るという恐怖。 本当に意図通り読み込みできているか、注意しましょう。

そこで、読み込みには SheetJS を使用し、データ処理自体は danfo.js を使うという方法を使います。 danfo.js のモジュールの中身を見てみると、内部では SheetJS(xlsx) を使用しているみたいなのですが...

問題なく読み込みできたケース

maclist ok 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 ng xlsx

このファイル(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 セルが空でないか、空かの違いしかありませんから、この空のセルの問題と思われます。

SheetJS を使って maclist-with-empty.xlsx を読み込む

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            ║
╚════════════╧═══════════════════╧═══════════════════╧═══════════════════╝

できました。