設計データ、部品表、計測値、設定ファイルをPythonで処理するとき、よく使われる形式がCSVとJSONです。
CSVは行と列からなる表形式に向き、JSONは辞書やリストによる階層構造を表せます。ただし、ファイルを読み込めただけでは安全に計算できません。
実務では、次の流れを分けて考えます。
読み込み
↓
構造の確認
↓
欠損・型・範囲の検証
↓
用途に合うデータへ変換
↓
計算
↓
CSVまたはJSONへ出力
この記事ではPython標準ライブラリだけを使い、CSVの測定・設計データを検証し、計算結果をJSONへ出力します。
今日の到達点
- CSVのrowとfieldを説明できる
csv.readerとcsv.DictReaderを使い分けられる- CSVから読み込んだ値を検証して数値へ変換できる
- JSON文字列とPythonオブジェクトを区別できる
json.load()・loads()・dump()・dumps()を使い分けられる- 欠損値、文字コード、非有限値を考慮できる
- 読み込み、検証、変換、出力を分離できる
CSVとJSONの役割
CSVは、同じ列構造を持つ複数レコードに向いています。
part_no,pressure_mpa,area_mm2
CYL-001,14.0,1000.0
CYL-002,7.0,500.0
この例では、1行が1件のレコードです。各列はfieldと呼ばれます。
JSONは、keyとvalueの組み合わせや配列、入れ子構造を表せます。
{
"part_no": "CYL-001",
"conditions": {
"pressure_mpa": 14.0,
"area_mm2": 1000.0
},
"enabled": true
}
CSVとJSONの大まかな選択基準です。
| データの特徴 | 適した形式 |
|---|---|
| 同じ列が並ぶ一覧 | CSV |
| 表計算ソフトとの受け渡し | CSV |
| 部品表・測定値一覧 | CSV |
| 階層構造や入れ子 | JSON |
| 設定・APIデータ | JSON |
| 数値・真偽値・nullを区別 | JSON |
どちらが上位という関係ではありません。入力はCSV、計算結果はJSON、集計結果はCSVというように、役割ごとに使い分けられます。
csvモジュールを使う理由
CSVは単純に見えますが、値の中にカンマ、引用符、改行が含まれる場合があります。
part_no,description
A001,"plate, machined"
これをline.split(",")で分割すると、description内のカンマまで区切りと誤認します。
Pythonのcsv標準ライブラリは、このような引用規則を考慮して読み書きします。CSVを手作業で分割せず、csv.readerやcsv.DictReaderを使います。
csv.readerで行を読む
csv.readerは、各行を文字列のリストとして返します。
import csv
with open(
"parts.csv",
mode="r",
encoding="utf-8",
newline="",
) as file:
reader = csv.reader(file)
for row in reader:
print(row)
入力が次の場合を考えます。
part_no,pressure_mpa,area_mm2
CYL-001,14.0,1000.0
出力です。
['part_no', 'pressure_mpa', 'area_mm2']
['CYL-001', '14.0', '1000.0']
すべてのfieldが文字列として返る点が重要です。14.0も自動的にはfloatになりません。
CSVファイルをcsvモジュールへ渡す場合、Python公式ドキュメントではnewline=""で開くよう示されています。これにより、CSVモジュールが改行を正しく処理できます。
DictReaderで列名から値を取り出す
ヘッダー付きCSVではcsv.DictReaderが便利です。各行が辞書として返ります。
import csv
with open(
"parts.csv",
mode="r",
encoding="utf-8",
newline="",
) as file:
reader = csv.DictReader(file)
for row in reader:
print(row["part_no"])
print(row["pressure_mpa"])
位置番号のrow[1]ではなく、意味を表すrow["pressure_mpa"]で参照できます。
ただし、辞書になっても値は文字列です。
pressure_text = row["pressure_mpa"]
pressure_mpa = float(pressure_text)
型変換が失敗する可能性があるため、検証と例外処理が必要です。
欠損値を区別する
次のCSVでは、2行目の圧力が空欄です。
part_no,pressure_mpa,area_mm2
CYL-001,14.0,1000.0
CYL-002,,800.0
空欄をfloat("")へ渡すとValueErrorになります。
def parse_required_float(
text: str,
field_name: str,
) -> float:
if text.strip() == "":
raise ValueError(f"{field_name} is missing")
return float(text)
欠損値を無条件に0.0へ置き換えると、「未入力」と「実測値0」を区別できなくなります。空欄を許可する項目なのか、必須項目なのかをデータ仕様として決めます。
数値の型と範囲を検証する
工学計算では、数値へ変換できるだけでは不十分です。NaNや無限大、負値などを除外する必要があります。
import math
def parse_positive_float(
text: str,
field_name: str,
) -> float:
if text.strip() == "":
raise ValueError(f"{field_name} is missing")
value = float(text)
if not math.isfinite(value):
raise ValueError(f"{field_name} must be finite")
if value <= 0:
raise ValueError(
f"{field_name} must be greater than 0"
)
return value
検証項目を分解すると、原因を追いやすくなります。
- 空欄ではないか
- 数値形式か
NaNや無限大ではないか- 許容範囲内か
- 想定単位と一致するか
単位はpressure_mpa、area_mm2のように列名へ含めると、変換規則を明確にできます。
CSVを書き出す
csv.writerは行のリストを出力します。
import csv
rows = [
["part_no", "force_n"],
["CYL-001", 14000.0],
["CYL-002", 3500.0],
]
with open(
"results.csv",
mode="w",
encoding="utf-8",
newline="",
) as file:
writer = csv.writer(file)
writer.writerows(rows)
列名と辞書を使う場合はcsv.DictWriterを使用できます。
import csv
records = [
{"part_no": "CYL-001", "force_n": 14000.0},
{"part_no": "CYL-002", "force_n": 3500.0},
]
fieldnames = ["part_no", "force_n"]
with open(
"results.csv",
mode="w",
encoding="utf-8",
newline="",
) as file:
writer = csv.DictWriter(
file,
fieldnames=fieldnames,
)
writer.writeheader()
writer.writerows(records)
出力内容です。
part_no,force_n
CYL-001,14000.0
CYL-002,3500.0
JSON文字列とPythonオブジェクトは別物
次の値はPythonの辞書です。
record = {
"part_no": "CYL-001",
"pressure_mpa": 14.0,
"enabled": True,
"note": None,
}
一方、次はJSON形式の文字列です。
json_text = """
{
"part_no": "CYL-001",
"pressure_mpa": 14.0,
"enabled": true,
"note": null
}
"""
見た目は似ていますが、型が異なります。
print(type(record))
print(type(json_text))
出力です。
<class 'dict'>
<class 'str'>
JSONでは真偽値がtrue・false、値なしがnullです。PythonではTrue・False・Noneです。
loadsとdumpsは文字列を変換する
json.loads()はJSON文字列をPythonオブジェクトへ変換します。
import json
json_text = """
{
"part_no": "CYL-001",
"pressure_mpa": 14.0
}
"""
record = json.loads(json_text)
print(record["part_no"])
print(type(record))
出力です。
CYL-001
<class 'dict'>
json.dumps()はPythonオブジェクトをJSON文字列へ変換します。
import json
record = {
"part_no": "CYL-001",
"force_n": 14000.0,
}
json_text = json.dumps(
record,
ensure_ascii=False,
indent=2,
allow_nan=False,
)
print(json_text)
print(type(json_text))
出力です。
{
"part_no": "CYL-001",
"force_n": 14000.0
}
dumpsの末尾のsは、文字列を返す操作だと覚えると区別しやすくなります。
loadとdumpはファイルを扱う
JSONファイルを読むにはjson.load()を使います。
import json
with open(
"cylinder.json",
mode="r",
encoding="utf-8",
) as file:
record = json.load(file)
print(record["part_no"])
ファイルへ書くにはjson.dump()を使います。
import json
record = {
"part_no": "CYL-001",
"force_n": 14000.0,
}
with open(
"result.json",
mode="w",
encoding="utf-8",
) as file:
json.dump(
record,
file,
ensure_ascii=False,
indent=2,
allow_nan=False,
)
file.write("\n")
主な使い分けです。
| 関数 | 入力 | 出力 |
|---|---|---|
json.loads() | JSON文字列 | Pythonオブジェクト |
json.dumps() | Pythonオブジェクト | JSON文字列 |
json.load() | 読み取り可能なファイル | Pythonオブジェクト |
json.dump() | Pythonオブジェクトとファイル | ファイルへJSON出力 |
JSONへ変換できる型
代表的な対応関係です。
| Python | JSON |
|---|---|
dict | object |
list・tuple | array |
str | string |
int・float | number |
True・False | true・false |
None | null |
すべてのPythonオブジェクトをそのままJSONへ変換できるわけではありません。
from pathlib import Path
import json
data = {"path": Path("result.json")}
json.dumps(data)
Pathは既定の変換対象ではないためTypeErrorになります。文字列へ変換するなど、データモデルをJSONで表現可能な型へ整えます。
data = {"path": str(Path("result.json"))}
ensure_asciiと日本語
json.dump()とjson.dumps()は、既定では非ASCII文字をエスケープします。
import json
data = {"status": "正常"}
print(json.dumps(data))
人が読むJSONとして日本語をそのまま出力したい場合はensure_ascii=Falseを指定します。
print(json.dumps(
data,
ensure_ascii=False,
indent=2,
))
出力です。
{
"status": "正常"
}
ファイル自体はUTF-8で開きます。
NaNとInfinityを出力しない
Pythonのjsonモジュールは既定ではNaNやInfinityを出力できますが、これらは厳密なJSON仕様の数値ではありません。外部システムとの交換では問題になる可能性があります。
import json
data = {"value": float("nan")}
json.dumps(
data,
allow_nan=False,
)
allow_nan=Falseを指定するとValueErrorが発生し、非標準の数値を出力前に検出できます。工学データでは、計算途中で生じた非有限値を黙って保存しない方が安全です。
JSONの異常系を処理する
不正なJSON文字列を読み込むとjson.JSONDecodeErrorが発生します。
import json
json_text = '{"pressure_mpa": 14.0,}'
try:
record = json.loads(json_text)
except json.JSONDecodeError as error:
print(
f"JSON error at line {error.lineno}, "
f"column {error.colno}: {error.msg}"
)
JSONでは末尾の余分なカンマや、キーを単一引用符で囲む書き方は無効です。
外部から受け取るJSONには、サイズ制限も必要です。Python公式ドキュメントでも、信頼できないJSONの解析はCPUやメモリを大量消費する可能性があるため、入力サイズを制限するよう注意されています。
実務例:CSVを検証してJSONへ変換する
次のCSVを入力とします。
part_no,pressure_mpa,area_mm2
CYL-001,14.0,1000.0
CYL-002,,800.0
CYL-003,high,1200.0
CYL-004,7.0,500.0
欠損や不正値を除外し、検証済みデータだけで理論推力を計算します。
import csv
import json
import math
def parse_positive_float(
text: str,
field_name: str,
) -> float:
if text.strip() == "":
raise ValueError(f"{field_name} is missing")
value = float(text)
if not math.isfinite(value) or value <= 0:
raise ValueError(
f"{field_name} must be a finite positive number"
)
return value
valid_records = []
errors = []
with open(
"cylinders.csv",
mode="r",
encoding="utf-8",
newline="",
) as file:
reader = csv.DictReader(file)
for row_no, row in enumerate(reader, start=2):
try:
pressure_mpa = parse_positive_float(
row["pressure_mpa"],
"pressure_mpa",
)
area_mm2 = parse_positive_float(
row["area_mm2"],
"area_mm2",
)
except (KeyError, TypeError, ValueError) as error:
errors.append({
"row_no": row_no,
"reason": str(error),
})
continue
valid_records.append({
"part_no": row["part_no"].strip(),
"pressure_mpa": pressure_mpa,
"area_mm2": area_mm2,
"force_n": pressure_mpa * area_mm2,
})
with open(
"results.json",
mode="w",
encoding="utf-8",
) as file:
json.dump(
valid_records,
file,
ensure_ascii=False,
indent=2,
allow_nan=False,
)
file.write("\n")
print(f"Valid: {len(valid_records)}")
print(f"Errors: {len(errors)}")
実行結果です。
Valid: 2
Errors: 2
results.jsonの内容です。
[
{
"part_no": "CYL-001",
"pressure_mpa": 14.0,
"area_mm2": 1000.0,
"force_n": 14000.0
},
{
"part_no": "CYL-004",
"pressure_mpa": 7.0,
"area_mm2": 500.0,
"force_n": 3500.0
}
]
除外理由は次のように保持できます。
[
{
"row_no": 3,
"reason": "pressure_mpa is missing"
},
{
"row_no": 4,
"reason": "could not convert string to float: 'high'"
}
]
主要な変換・検証ロジックはPython 3.12.14で実行確認済みです。
この例で重要なのは、形式を変換しただけではない点です。
- CSVを辞書として読み込む
- 必須項目を確認する
- 文字列を数値へ変換する
- 有限の正数であることを確認する
- 検証済みデータだけ計算する
- 数値型を保持したJSONへ出力する
- 除外した行番号と理由を別に残す
よくある失敗
CSVの数値を文字列のまま使う
CSVから読んだ"14"は文字列です。加算や比較の前に、仕様に沿ってintまたはfloatへ変換します。
空欄をすべて0へ変換する
未入力、計測不能、該当なし、実測値0は別の状態です。欠損値の意味をデータ仕様として定義します。
CSVをsplitで処理する
引用符で囲まれたカンマや改行を正しく扱えません。csvモジュールを使用します。
JSON文字列を辞書だと思う
json.dumps()の戻り値はstrです。辞書として参照するにはjson.loads()でPythonオブジェクトへ戻します。
dumpを同じファイルへ繰り返す
JSONは複数オブジェクトを区切るフレーム形式ではありません。同じファイルへjson.dump()を連続実行すると、通常は1個の有効なJSON文書になりません。複数件はリストへまとめるか、JSON Linesなど目的に合う形式を別途設計します。
ファイル形式だけで妥当性を判断する
JSONとして構文が正しくても、圧力が負、単位が不明、必須キーがないといった意味上の問題は残ります。構文検証と業務ルール検証を分けます。
練習問題
CSVの数値を変換する
次の行から流量をfloatへ変換してください。
row = {
"line_name": "Line A",
"flow_l_min": "60.0",
}
解答例です。
flow_l_min = float(row["flow_l_min"])
flow_l_s = flow_l_min / 60
print(f"Flow: {flow_l_s:.2f} L/s")
出力です。
Flow: 1.00 L/s
JSON文字列を辞書へ変換する
import json
json_text = '{"pressure_mpa": 14.0}'
record = json.loads(json_text)
print(record["pressure_mpa"])
print(type(record))
出力です。
14.0
<class 'dict'>
Pythonデータを日本語入りJSONへ変換する
import json
result = {
"part_no": "CYL-001",
"status": "正常",
"force_n": 14000.0,
}
json_text = json.dumps(
result,
ensure_ascii=False,
indent=2,
allow_nan=False,
)
print(json_text)
確認観点は、日本語をそのまま表示し、非有限値を許可しない設定にしていることです。
まとめ
- CSVは同じ列を持つ複数レコードに向く
- JSONはkey/value、配列、入れ子構造を表せる
- CSVのfieldは基本的に文字列として読み込まれる
- CSVは
split(",")ではなくcsvモジュールで処理する - CSVファイルは
newline=""を指定して開く DictReaderとDictWriterで列名を基準に処理できる- 欠損、型、有限性、範囲、単位を検証してから計算する
loads・dumpsはJSON文字列を扱うload・dumpはファイルオブジェクトを扱う- JSON文字列とPythonの辞書・リストは別の型である
ensure_ascii=Falseで日本語をそのまま出力できるallow_nan=Falseで非標準の非有限値を検出できる- 変換できなかった行は、行番号と理由を残す
CSVやJSONはデータを保存する器です。再利用可能なデータへ変えるには、項目名、型、単位、必須条件、許容範囲を定義し、読み込みと検証を分離する必要があります。

