はじめに
CSVファイルをPythonのpandasや各種パーサーで読み込んだ際、以下のような「列数の不一致エラー」に遭遇したことはありませんか?
# pandasでのエラー例
ParserError: Error tokenizing data. C error: Expected 13 fields in line 14, saw 14
# 一般的なパーサーでのエラー例
Record 14 has 14 fields but the header has 13. Fix the column counts first.
このエラーの代表的な原因の1つが、テキストデータ内に含まれる「3桁区切りの数字カンマ」 です。
例えば、文章内の 全社(2,489社) や金額 1,000,000 がダブルクォートで囲まれていないケースです。この場合、パーサーはカンマを「列の区切り文字(Delimiter)」と誤認し、1つのセルを2列に分割してしまいます。
本記事では、この列ズレエラー(Ragged Rows)が発生するメカニズムと、データを破壊せずに安全に特定・修正するための実践手順を解説します。
やってはいけない2つの悪手(サイレントデータ破壊のリスク)
急いでエラーを解消しようとして、以下のような安易なスクリプト処理を行うと、重要なデータを破損させる原因になります。
1. 正規表現による一括カンマ削除
// 危険な例: 数字で挟まれたカンマを無条件で削除する
text = text.replace(/(\d+),(\d+)/g, "$1$2");
この処理を行うと、郵便番号や日付、各種コード値(例: 12,34)、あるいは「本当に数値カラムが2列並んでいる正常なデータ」まで誤って結合されてしまい、サイレントなデータ破壊を引き起こします。
2. 不足列・余剰列の自動推測マージ
「列数が1つ多いから、最後の2列を自動でくっつければ良いだろう」という安易な補正も極めて危険です。どの列でカンマの誤分割が起きたのかは機械的には判断できません。列の対応関係が全体的に1列ずつズレたまま保存され、AIの評価データや業務DBを汚染することになります。
安全な解決アプローチ(RFC 4180準拠)
唯一安全な恒久対策は、「不整合が起きている正確な行番号(Line)を特定し、カンマを含む該当フィールドをRFC 4180に従ってダブルクォートで囲む」 ことです。
# 修正前(カンマで列が分断される)
14,調査結果,集計対象が全社(2,489社)であること,完了
# 修正後(RFC 4180に準拠してクォートで囲む)
14,調査結果,"集計対象が全社(2,489社)であること",完了
実践コードと実装のポイント
1. 不整合行を正確に特定するパーサー設計(TypeScript)
パーサー側では、列数が合わないレコードに遭遇した際、勝手にパディング(空文字追加)やトランケート(切り捨て)を行わず、正確なソース行番号(Line)を保持して安全に例外を送出することが重要です。
export interface ParsedCsv {
headers: string[];
rows: string[][];
}
export function parseCsvSafe(text: string, delimiter = ","): ParsedCsv {
if (!text.trim()) throw new Error("The file is empty.");
const records: string[][] = [];
let field = "";
let record: string[] = [];
let line = 1;
const recordLines: number[] = [];
let currentRecordStartLine = 1;
for (let i = 0; i < text.length; i++) {
const ch = text[i];
// 改行処理(CRLF / LF)
if (ch === "\r" || ch === "\n") {
recordLines.push(currentRecordStartLine);
record.push(field);
records.push(record);
record = [];
field = "";
if (ch === "\r" && text[i + 1] === "\n") i++;
line++;
currentRecordStartLine = line;
} else if (ch === delimiter) {
record.push(field);
field = "";
} else {
field += ch;
}
}
// 最終行の追加
if (field || record.length > 0) {
recordLines.push(currentRecordStartLine);
record.push(field);
records.push(record);
}
// 列数不一致(Ragged Rows)の厳密判定
const [headers, ...rows] = records;
const expectedCols = headers.length;
records.forEach((r, idx) => {
if (r.length !== expectedCols) {
const lineNum = recordLines[idx] ?? (idx + 1);
throw new Error(
`Line ${lineNum} (Record ${idx + 1}) has ${r.length} fields but the header has ${expectedCols}. Fix the column counts first.`
);
}
});
return { headers, rows };
}
2. Python (pandas) での安全なトラブルシューティング
pandasで巨大なCSVを読み込む際、エラー行をスキップせずに特定したい場合は、標準ライブラリの csv モジュールを組み合わせて行番号を特定します。
import csv
def find_ragged_rows(csv_path: str):
with open(csv_path, "r", encoding="utf-8") as f:
reader = csv.reader(f)
header = next(reader)
expected_cols = len(header)
for line_num, row in enumerate(reader, start=2):
if len(row) != expected_cols:
print(f"⚠️ 列数不一致: 行 {line_num} (期待値: {expected_cols}列, 実際: {len(row)}列)")
print(f" 内容: {row}")
break
find_ragged_rows("data.csv")
3. 再保存時のクォート付与(シリアライズ設計)
修正後のデータを保存する際は、カンマや改行、既存のクォートを含むセルのみを確実にエスケープします。
function quoteField(value: string, delimiter = ","): string {
// カンマ、改行、ダブルクォートが含まれている場合のみクォート
const needsQuote = value.includes(delimiter) || /["\r\n]/.test(value);
return needsQuote ? `"${value.replace(/"/g, '""')}"` : value;
}
エディタ上で瞬時に問題行へジャンプするUX
巨大なCSVファイルの中で、エラーメッセージに表示された行番号を目視でスクロールして探すのは手間がかかります。
VS Codeなどのエディタ上では、ショートカット Ctrl + G(macOS: Cmd + G)を押して :14 のように行番号を入力すれば、該当行へ瞬時にジャンプできます。
また、VS Code拡張機能「Structured Data Editor」などを利用する手法もあります。
列数の不一致を検知した際にトースト通知から「Go to Line X」ボタンで該当行へ1クリックでジャンプし、余分に分割されたセルを安全に修正できます。
まとめ
CSVの列数不一致エラー(Record X has Y fields)は、安易な正規表現置換や自動マージに頼るとサイレントなデータ破壊を招きます。
- パーサーで行番号を正確に特定する。
- 3桁カンマや記号を含んだ該当セルを
""(ダブルクォート)で囲む。 - RFC 4180 に準拠した安全なシリアライズを行う。
この3つの原則を守ることで、大切な業務データやプロンプト評価データの安全性を確実に維持できます。
関連記事・お問い合わせ
-
開発背景・アーキテクチャの解説:
本ツールの設計思想や、スクロール制御・パフォーマンス最適化などの実装裏話は Zennの記事 にまとめています。 -
業務導入・受託開発のご相談:
AI Nexusでは、業務効率化ツールの受託開発やAI導入コンサルティングを行っています。詳細は AI Nexus 公式サイト をご覧ください。