補助ペインで文脈の残量を見ながら、6月からの売上レポートを添付したところでした。1,730 行、127 KB。テキストとしては小さいほうです。それでも、質問を書き終える前に残量の目盛りが目に見えて減っていました。
そのときは深く考えず、期間を半分に切って渡し直しました。気になったのは翌日です。同じ中身でも、書き出す形を変えるだけで倍以上ちがうのではないか。だとすれば、削るべきは行数ではなく書き方なのかもしれません。
個人開発では、こうした小さな摩擦が毎週同じ場所で戻ってきます。売上を確認するたびに期間を切り直すのは、作業としては数十秒でも、判断を鈍らせるには十分な長さでした。
2.4.3 では、入力欄へ .json・.md・.csv を添付できるようになりました。形式ごとにバッジが付き、貼り付けではなく添付として渡せます。選択肢が増えたぶん、どの形で渡すかという判断がこちらに移ってきたことになります。
この記事は、その判断を勘ではなく数字で決めるために書いた計測の記録です。
なお本稿の数値は、実際のレポートをそのまま公開できないため、同じ列構成・同じ規模の表を固定シードで生成して測っています。生成スクリプトも載せますので、手元で同じ数字が出ることを確認していただけます。
トークナイザについて先に断っておくこと
計測には OpenAI の tiktoken(o200k_base)を使っています。Antigravity が使う Gemini 系のトークナイザとは別物です。絶対値をそのまま請求額に換算することはできません。
それでも相対比較の指標としては使えると考えています。同じ計測を cl100k_base でも回したところ、後述する削減率は 0.476 と 0.483 でほぼ一致しました。形式や列構成による差は、トークナイザの違いよりずっと大きいのです。
以下、倍率と割合を読んでいただき、絶対値は目安として扱っていただければ幸いです。
題材にする表
App Store Connect の売上レポートと同じ列構成の表です。62 日 × 9 か国 × 4 SKU から、売上ゼロの行を一部落として 1,730 行になりました。
# mkdata.py — 計測用のデータを固定シードで作る
import csv, random, datetime, os
random.seed( 20260801 )
countries = [ "JP" , "US" , "GB" , "DE" , "FR" , "KR" , "TW" , "CA" , "AU" ]
skus = [
( "WLP.PRO.YEAR" , "Wallpaper Pro (Yearly)" ),
( "WLP.PRO.MONTH" , "Wallpaper Pro (Monthly)" ),
( "CALM.REMOVEADS" , "Calm Sounds Remove Ads" ),
( "CALM.PACK.02" , "Calm Sounds Pack 02" ),
]
rate = { "JP" : 1.0 , "US" : 150.0 , "GB" : 190.0 , "DE" : 163.0 , "FR" : 163.0 ,
"KR" : 0.11 , "TW" : 4.7 , "CA" : 110.0 , "AU" : 98.0 }
cur = { "JP" : "JPY" , "US" : "USD" , "GB" : "GBP" , "DE" : "EUR" , "FR" : "EUR" ,
"KR" : "KRW" , "TW" : "TWD" , "CA" : "CAD" , "AU" : "AUD" }
base = { "JP" : 40 , "US" : 22 , "GB" : 6 , "DE" : 7 , "FR" : 4 , "KR" : 9 , "TW" : 5 , "CA" : 3 , "AU" : 3 }
price = { "WLP.PRO.YEAR" : 3800.0 , "WLP.PRO.MONTH" : 480.0 ,
"CALM.REMOVEADS" : 320.0 , "CALM.PACK.02" : 250.0 }
mix = { "WLP.PRO.YEAR" : 0.12 , "WLP.PRO.MONTH" : 0.46 ,
"CALM.REMOVEADS" : 0.28 , "CALM.PACK.02" : 0.14 }
d0 = datetime.date( 2026 , 5 , 31 )
rows = []
for i in range ( 62 ):
d = d0 + datetime.timedelta( days = i)
wk = 1.18 if d.weekday() >= 5 else 1.0 # 週末は少し伸びる
for c in countries:
for sku, name in skus:
lam = base[c] * mix[sku] * wk
units = max ( 0 , int (random.gauss(lam, lam * 0.45 )))
if units == 0 and random.random() < 0.55 :
continue # ゼロ行の一部は出力されない
proceeds = units * price[sku] * 0.70 / rate[c]
rows.append({
"date" : d.isoformat(),
"country_code" : c,
"product_sku" : sku,
"product_name" : name,
"device_type" : random.choice([ "iPhone" , "iPad" ]),
"units_sold" : units,
# 為替換算の結果、小数がそのまま残る。実際のレポートでもよく見る形
"developer_proceeds" : repr ( round (proceeds, 10 )),
"currency_code" : cur[c],
})
p = os.path.expanduser( "~/lab/sales.csv" )
os.makedirs(os.path.dirname(p), exist_ok = True )
with open (p, "w" , newline = "" ) as f:
w = csv.DictWriter(f, fieldnames = list (rows[ 0 ].keys()))
w.writeheader(); w.writerows(rows)
print ( "rows:" , len (rows), "bytes:" , os.path.getsize(p))
# => rows: 1730 bytes: 127536
developer_proceeds に 24181.8181818182 のような値が並ぶのがこの表の特徴です。為替換算を挟んだレポートでは珍しくありません。ここが後で効いてきます。
同じ表を8つの形で書き出して、並べる
まず形式そのものの差を見ます。同じ 1,730 行を CSV・TSV・Markdown 表・JSON・YAML 風に書き出し、トークン数を測るだけの短いスクリプトです。
#!/usr/bin/env python3
# attach_cost.py — 添付する表データを、形式ごとにトークン単価で比べる
import csv, io, json, argparse
import tiktoken
ENC = tiktoken.get_encoding( "o200k_base" )
def ntok (s): return len ( ENC .encode(s))
def load (path):
with open (path, newline = "" , encoding = "utf-8" ) as f:
r = csv.DictReader(f)
return r.fieldnames, list (r)
def as_csv (cols, rows):
buf = io.StringIO()
w = csv.DictWriter(buf, fieldnames = cols, lineterminator = " \n " )
w.writeheader(); w.writerows(rows)
return buf.getvalue()
def as_tsv (cols, rows):
out = [ " \t " .join(cols)]
out += [ " \t " .join( str (r[c]) for c in cols) for r in rows]
return " \n " .join(out) + " \n "
def as_json_pretty (cols, rows):
return json.dumps([{c: r[c] for c in cols} for r in rows],
ensure_ascii = False , indent = 2 )
def as_json_compact (cols, rows):
return json.dumps([{c: r[c] for c in cols} for r in rows],
ensure_ascii = False , separators = ( "," , ":" ))
def as_json_split (cols, rows):
# 列名を1回だけ書き、値は配列の配列で持つ
return json.dumps({ "columns" : cols,
"data" : [[r[c] for c in cols] for r in rows]},
ensure_ascii = False , separators = ( "," , ":" ))
def as_md_padded (cols, rows):
w = [ max ( len (c), * ( len ( str (r[c])) for r in rows)) for c in cols]
line = lambda cells: "| " + " | " .join(
str (v).ljust(w[i]) for i, v in enumerate (cells)) + " |"
out = [line(cols), "|" + "|" .join( "-" * (x + 2 ) for x in w) + "|" ]
out += [line([r[c] for c in cols]) for r in rows]
return " \n " .join(out) + " \n "
def as_md_tight (cols, rows):
out = [ "|" + "|" .join(cols) + "|" , "|" + "|" .join( "-" for _ in cols) + "|" ]
out += [ "|" + "|" .join( str (r[c]) for c in cols) + "|" for r in rows]
return " \n " .join(out) + " \n "
def as_yaml (cols, rows):
out = []
for r in rows:
out.append( "- " + f " { cols[ 0 ] } : { r[cols[ 0 ]] } " )
for c in cols[ 1 :]:
out.append( f " { c } : { r[c] } " )
return " \n " .join(out) + " \n "
RENDERERS = [
( "CSV" , as_csv),
( "TSV" , as_tsv),
( "Markdown (padded)" , as_md_padded),
( "Markdown (tight)" , as_md_tight),
( "JSON records indent=2" , as_json_pretty),
( "JSON records compact" , as_json_compact),
( "JSON columnar" , as_json_split),
( "YAML-like" , as_yaml),
]
def report (cols, rows):
base = None
print ( f " { 'format' :<24 }{ 'bytes' :>10 }{ 'tokens' :>10 }{ 'ratio' :>8 }{ 'tok/row' :>9 } " )
for name, fn in RENDERERS :
s = fn(cols, rows)
t = ntok(s)
if base is None :
base = t
print ( f " { name :<24 }{ len (s.encode()) :>10 }{ t :>10 } "
f " { t / base :>7.2f } x { t / len (rows) :>9.1f } " )
if __name__ == "__main__" :
ap = argparse.ArgumentParser()
ap.add_argument( "csv_path" )
ap.add_argument( "--rows" , type = int , default = 0 )
a = ap.parse_args()
cols, rows = load(a.csv_path)
if a.rows:
rows = rows[:a.rows]
report(cols, rows)
結果です。CSV を 1.00 とした倍率で並べています。
形式 バイト数 トークン数 CSV比 1行あたり
CSV(書き出したまま) 125,805 56,565 1.00x 32.7
TSV 125,805 56,562 1.00x 32.7
Markdown 表(桁を揃える) 237,284 76,788 1.36x 44.4
Markdown 表(詰める) 129,285 60,208 1.06x 34.8
JSON 配列(indent=2) 449,218 151,864 2.68x 87.8
JSON 配列(区切りを詰める) 355,797 104,296 1.84x 60.3
JSON 列指向(columns + data) 156,983 61,076 1.08x 35.3
YAML 風 338,496 121,838 2.15x 70.4
二つ、予想と食い違った点がありました。
ひとつは Markdown 表です。桁を揃えると 1.36 倍になりました。差の 16,580 トークンは、ほぼすべて桁揃えのために差し込まれた空白です。実際に数えたところ 80,164 個の空白が入っており、空白 5 個弱ごとに 1 トークンが消えている計算になります。整形ツールを通してから添付する習慣があると、読みやすさの代金をここで払っていることになります。
もうひとつは JSON です。「JSON は高い」と漠然と思っていましたが、高いのは形式ではなく形でした。列指向にすると 1.08 倍まで下がります。つまり高くつくのはレコード指向、列名を行数だけ繰り返す書き方のほうです。
「JSON は高い」は、行数のせいではない
では小さい表なら気にしなくてよいのか。行数を変えて倍率の動きを見ました。
行数 CSV JSON(indent=2) 差 倍率
5 184 435 251 2.36x
20 696 1,773 1,077 2.55x
100 3,306 8,791 5,485 2.66x
300 9,818 26,323 16,505 2.68x
1,730 56,565 151,864 95,299 2.68x
倍率は 5 行の時点で既に 2.36 倍あり、そこからほとんど動きません。列名の繰り返しは 1 行目から始まっているのですから、当然といえば当然です。
ここから引ける線は、割合ではなく絶対量で判断するということでした。20 行なら差は 1,077 トークンで、形式にこだわる価値はありません。300 行を超えたあたりから、差が「もう一往復ぶんの会話」に相当し始めます。
判断の軸をコンテキスト量そのものに置く考え方は、Pro に回す件数を半分にしても、トークンは6割残っていた で扱ったモデル振り分けの基準とも重なります。件数ではなく量で見ると、直感がずれている箇所が見つかります。
列ごとに値段をつける
形式の差は最大でも 2.68 倍です。中身のほうがもっと動くのではないか。そう思って、列を 1 つずつ抜いて差分を取ることにしました。
#!/usr/bin/env python3
# col_cost.py — 1列ずつ抜いて、その列が何トークン分を占めているかを差分で測る
import csv, io, sys
import tiktoken
ENC = tiktoken.get_encoding( "o200k_base" )
def ntok (s): return len ( ENC .encode(s))
def render (cols, rows):
buf = io.StringIO()
# extrasaction="ignore" を付けないと、抜いた列が残っている行で例外になる
w = csv.DictWriter(buf, fieldnames = cols,
extrasaction = "ignore" , lineterminator = " \n " )
w.writeheader(); w.writerows(rows)
return buf.getvalue()
def main (path):
with open (path, newline = "" , encoding = "utf-8" ) as f:
r = csv.DictReader(f)
cols, rows = r.fieldnames, list (r)
full = ntok(render(cols, rows))
print ( f "全体: { full } tokens / { len (rows) } 行" )
print ( f " { '列名' :<22 }{ '欠くと減る量' :>12 }{ '占有率' :>9 }{ 'tok/セル' :>10 } " )
for c in cols:
rest = [x for x in cols if x != c]
d = full - ntok(render(rest, rows))
print ( f " { c :<22 }{ d :>12 }{ d / full * 100 :>8.1f } % { d / len (rows) :>10.2f } " )
if __name__ == "__main__" :
main(sys.argv[ 1 ])
最初に書いたときは extrasaction="ignore" を付け忘れ、抜いたはずの列が行の辞書に残ったまま ValueError で落ちました。行の辞書を作り直さないといけないのか、と一瞬遠回りしかけた罠です。対処は引数ひとつでしたが、同じところで手が止まる方がいそうなのでコメントとして残してあります。
出力です。
列名 欠くと減るトークン 占有率 1セルあたり
date 12,112 21.4% 7.00
product_name 11,123 19.7% 6.43
developer_proceeds 10,225 18.1% 5.91
product_sku 9,029 16.0% 5.22
device_type 4,334 7.7% 2.51
currency_code 3,686 6.5% 2.13
units_sold 3,464 6.1% 2.00
country_code 3,463 6.1% 2.00
売上表でいちばん高い列が日付だったのは、正直なところ意外でした。金額が並ぶ列を疑っていたのです。
理由は単純で、2026-05-31 が 7 トークンになるからです。数字とハイフンが交互に並ぶ文字列は、まとまったトークンになりにくい。それが 1,730 行ぶん積み上がります。1 セル 2 トークンの country_code の 3.5 倍です。
そして 2 位の product_name は、product_sku から一意に決まる列でした。情報としては何も足していないのに、全体の 5 分の 1 を使っています。
効く順に削る — 四つの手当てを実測する
見つかった無駄に、順に手を入れました。すべて元の CSV(56,565 トークン)からの減少率です。
手当て トークン数 削減率
日付を起点からの通日に置き換える 47,935 15.3%
金額を小数2桁に丸める 53,663 5.1%
product_name を凡例へ外出しする 45,503 19.6%
上の3つをまとめて適用 33,968 39.9%
+ 通貨を円換算の整数へ統一 26,934 52.4%
手当ての中身は短いので、そのまま載せます。
import csv, datetime, json
d0 = min (datetime.date.fromisoformat(r[ "date" ]) for r in rows)
# 1) 日付 -> 起点からの通日。凡例を1行添えて可逆にしておく
rows_day = [ dict (r, day = (datetime.date.fromisoformat(r[ "date" ]) - d0).days)
for r in rows]
legend_day = f "# day: { d0.isoformat() } を 0 とした経過日数 \n "
# 2) 金額 -> 小数2桁
rows_round = [ dict (r, developer_proceeds = f ' { float (r[ "developer_proceeds" ]) :.2f } ' )
for r in rows]
# 3) product_name -> 凡例へ外出し(sku から一意に決まるので本体からは外せる)
legend_sku = {r[ "product_sku" ]: r[ "product_name" ] for r in rows}
legend_line = "# product_sku: " + json.dumps(legend_sku, ensure_ascii = False ) + " \n "
日付については、通日に置き換える以外にもう一つ試しました。日付でグループ化して見出し行に一度だけ書く方法です。これは 46,475 トークン(17.8% 減)で、通日化の 15.3% をわずかに上回りました。
それでも通日化のほうを採っています。グループ化すると平らな表でなくなり、グループごとにヘッダ行が増え、受け取った側が再び 1 枚の表として扱いにくくなるためです。2.5 ポイントの差より、形が崩れないことを優先しました。
ここで一番の驚きは、金額の丸めが 5.1% しか効かなかったことでした。developer_proceeds は全体の 18.1% を占めているのに、24181.8181818182 を 24181.82 にしても、そのぶんが丸ごと消えるわけではありません。
理由は分布にありました。4368.0 のように元から短い円建ての行が 245 行あり、長い小数が並ぶのは為替換算を経た国の行だけです。列の占有率が高いことと、その列に削り代があることは別の話でした。
削り代がいちばん大きかったのは、精度でも日付でもなく、他の列から復元できる文字列の列 です。ここは 19.6% がまるごと落ちます。
一方で、削ってはいけないものもあります。通貨の統一(52.4% まで到達する手当て)は、換算レートを固定した時点で元に戻せません。私は集計の答えだけが欲しいときに限ってこれを使い、原票を見せたいときは 39.9% で止めています。
削減を自動的な予算として運用に組み込む方向は、並行エージェントのトークンコストを予算で抑える で扱った考え方と接続します。手で削るのは最初の一回で十分で、二回目からは仕組みに任せたいところです。
全部渡すのをやめる — 1,062 トークンの要約
そもそも 1,730 行を渡す必要があったのか、という問いが残ります。私が尋ねたかったのは「どの国で伸びているか」でした。行そのものではなく、輪郭が要る質問です。
そこで、表の輪郭だけを書き出すスクリプトを用意しました。
#!/usr/bin/env python3
# digest.py — 表データを「そのまま渡す」代わりに、輪郭だけを渡すための要約を作る
import csv, io, re, sys, argparse, statistics
from collections import Counter, defaultdict
import tiktoken
ENC = tiktoken.get_encoding( "o200k_base" )
DATE = re.compile( r " ^\d {4} - \d {2} - \d {2} $ " )
def is_num (v):
try :
float (v); return True
except ( TypeError , ValueError ):
return False
def qs (nums):
nums = sorted (nums)
q = lambda p: nums[ min ( len (nums) - 1 , int ( len (nums) * p))]
return nums[ 0 ], q( .5 ), q( .9 ), nums[ - 1 ]
def describe_num (name, vals, unit = None ):
nums = [ float (v) for v in vals if is_num(v)]
if not nums:
return f "- { name } : 数値なし"
lo, p50, p90, hi = qs(nums)
u = f " [ { unit } ]" if unit else ""
return ( f "- { name }{ u } : n { len (nums) } / min { lo :g } p50 { p50 :g } "
f "p90 { p90 :g } max { hi :g } / 合計 { sum (nums) :.6g } "
f "平均 { statistics.mean(nums) :.4g } " )
def build (cols, rows, unit_map, sample = 8 , topn = 5 ):
out = [ f "# 表の要約 / 行数 { len (rows) } / 列数 { len (cols) } " , "" , "## 列ごとの輪郭" ]
for c in cols:
vals = [r[c] for r in rows]
uniq = sorted ( set (vals))
blank = sum ( 1 for v in vals if v == "" )
if vals and all ( DATE .match(v) for v in vals if v):
# 日付は上位頻度ではなく範囲で示す(頻度を並べても意味がない)
out.append( f "- { c } : 日付 / 欠損 { blank } / "
f " { min (uniq) } 〜 { max (uniq) } / 実日数 { len (uniq) } " )
elif c in unit_map:
# 単位が混じる列は、単位ごとに分けないと統計が嘘になる
ukey = unit_map[c]
out.append( f "- { c } : 数値(単位列 { ukey } により混在 → 単位ごとに集計)"
f "/ 欠損 { blank } " )
groups = defaultdict( list )
for r in rows:
groups[r[ukey]].append(r[c])
for k in sorted (groups, key =lambda k: - len (groups[k])):
out.append( " " + describe_num(c, groups[k], unit = k))
elif len (uniq) > 12 and all (is_num(v) for v in vals if v):
out.append(describe_num(c, vals))
else :
top = Counter(vals).most_common(topn)
body = " " .join( f " { k } ( { v } )" for k, v in top)
more = f " …他 { len (uniq) - len (top) } 種" if len (uniq) > len (top) else ""
out.append( f "- { c } : カテゴリ / 欠損 { blank } / 一意 { len (uniq) } / "
f "上位 { body }{ more } " )
out += [ "" , f "## 先頭 { sample } 行(そのままの形)" , "```csv" ]
b = io.StringIO()
w = csv.DictWriter(b, fieldnames = cols, lineterminator = " \n " )
w.writeheader(); w.writerows(rows[:sample])
out += [b.getvalue().rstrip(), "```" ]
return " \n " .join(out) + " \n "
if __name__ == "__main__" :
ap = argparse.ArgumentParser()
ap.add_argument( "csv_path" )
ap.add_argument( "--unit-of" , action = "append" , default = [], metavar = "値列=単位列" )
a = ap.parse_args()
unit_map = dict (x.split( "=" , 1 ) for x in a.unit_of)
with open (a.csv_path, newline = "" , encoding = "utf-8" ) as f:
r = csv.DictReader(f)
cols, rows = r.fieldnames, list (r)
d = build(cols, rows, unit_map)
b = io.StringIO()
w = csv.DictWriter(b, fieldnames = cols, lineterminator = " \n " )
w.writeheader(); w.writerows(rows)
sys.stderr.write( f "[全量] { len ( ENC .encode(b.getvalue())) } tok / "
f "[要約] { len ( ENC .encode(d)) } tok \n " )
print (d)
実行結果は 1,062 トークン。全量の 1.9% です。
$ python3 digest.py sales.csv --unit-of developer_proceeds=currency_code
[全量] 56565 tok / [要約] 1062 tok
# 表の要約 / 行数 1730 / 列数 8
## 列ごとの輪郭
- date: 日付 / 欠損 0 / 2026-05-31 〜 2026-07-31 / 実日数 62
- country_code: カテゴリ / 欠損 0 / 一意 9 / 上位 JP(245) US(244) KR(223) DE(193) GB(189) …他 4 種
- product_sku: カテゴリ / 欠損 0 / 一意 4 / 上位 WLP.PRO.MONTH(518) CALM.REMOVEADS(472) ...
- units_sold: n 1730 / min 0 p50 1 p90 8 max 54 / 合計 5283 平均 3.054
- developer_proceeds: 数値(単位列 currency_code により混在 → 単位ごとに集計)/ 欠損 0
- developer_proceeds [EUR]: n 357 / min 0 p50 1.37423 p90 6.18405 max 16.319 / 合計 1047.47 平均 2.934
- developer_proceeds [JPY]: n 245 / min 0 p50 3360 p90 13300 max 21280 / 合計 1.29312e+06 平均 5278
- developer_proceeds [USD]: n 244 / min 0 p50 10.4533 p90 38.08 max 88.6667 / 合計 4385.59 平均 17.97
...
52,000 トークンぶんの行を渡さずに、聞きたかったことの手前までは届いています。
要約が危ないのは、間違いが数字の顔をして出てくるところ
この要約、最初の版では単位を見ていませんでした。そのときの出力がこれです。
- developer_proceeds: 数値 / 欠損 0 / 一意 156 / min 0 p50 4.48 p90 6109.09 max 24436.4 / 合計 3.292e+06
p50 4.48 に対して p90 6109.09。1,300 倍以上の開きです。円とウォンとユーロを同じ列に積んで中央値を取れば、そうなります。分布の話でも異常値の話でもなく、ただ単位が違う数を混ぜただけでした。
厄介なのは、この行が一見それらしく読めることです。生の CSV を渡していれば currency_code が横に並んでいるので、読む側が気づける余地がありました。要約は、その余地を消したうえで、計算済みの数字という顔で出てきます。
だから --unit-of developer_proceeds=currency_code という宣言を必須に近い形で足しました。値の列と単位の列の対応は、機械には推測させず、こちらが書きます。要約を作るときは、要約の過程で消える文脈を先に列挙する 。それが唯一まともに効いた対策でした。
なお日付列も、初版では「上位 5 件の出現頻度」を出していました。62 日ぶんの日付の頻度上位を並べても読む価値はありません。範囲と実日数に切り替えたのが上の版です。要約の設計は、列の型ではなく「その列で何を聞かれるか」で決めるべきでした。
実際にどう使い分けているか
測ったあとで、自分の手順はこうなりました。
状況 渡し方 理由
〜200行 / 中身を1行ずつ見てほしい CSV をそのまま添付 差は数千トークン。整形の手間のほうが高い
数百〜数千行 / 原票のまま議論したい 削った CSV(通日・凡例・丸め) 可逆のまま 4 割落とせる
傾向・偏り・異常だけ知りたい 要約のみ 2% で足りる。単位の宣言を忘れない
特定の集計の答えが欲しい 手元で集計してから結果を貼る そもそも表を渡す必要がない
JSON でしか出せない 列指向に組み替える レコード指向のままだと 2.68 倍
迷いやすいのは2行目と3行目の境目です。この場合は「返ってきた答えを、あとで原票と突き合わせる必要があるか」だけを見て決めています。突き合わせるつもりがあるなら削った CSV、傾向だけ掴んで次の質問に進むなら要約です。
そして、Markdown 表を添付するときは整形ツールを通しません。桁が揃っていないと読みにくいのは人間の側の事情で、渡す相手が読むのは文字列です。
測ること自体は安い
ここまでのスクリプトは合わせて 200 行ほどで、実行は数秒です。書いたのは一度きりで、以降は col_cost.py に CSV を投げれば、どの列が高いかがすぐ出ます。
添付できる形式が増えるということは、選ぶ責任がこちらに来るということでもあります。とはいえ、選ぶための材料は数分で揃いました。勘で削って必要な列を落としてしまうより、測ってから削るほうが結局は速かったというのが、今回いちばん残った実感です。
私はこの計測を、レポートを開く前の準備運動くらいの位置に置いています。列の値段が分かっていれば、どこまで削れば足りるかを毎回考え直さずに済みます。
次に手を入れるなら、この計測をレポート書き出しの後段に挟んで、添付する前に「この形なら何トークン」と出るようにするつもりです。判断のたびに測り直す必要がなくなれば、迷う時間そのものが消えます。
お読みいただきありがとうございました。手元の表でも同じ傾向が出るか、col_cost.py あたりから試していただければ嬉しいです。