min117の日記

初期desireもち。趣味Mac,メインFedora,仕事xp。

python3 カンマ区切りCSVファイル内の「金額列のカンマ区切り」のみ消し去る

f:id:min117:20201108230251p:plain

例えばこんな財務データファイルがある。

irbank.net

ファイル名はSheet1a.csvで保存。

 

売上高列に続く数字は "116,861,000,000円 "

f:id:min117:20201108221123p:plain

つまり、金額がカンマで区切られてしまっている。これだとCSVの各項目(フィールド)の区切りのカンマなのか金額のカンマなのか、区別がつかない。これだと処理しづらい。

 

国税庁も「財務諸表では金額の区切りに半角文字のカンマを使うな」と注意している。

f:id:min117:20201108121648p:plain

www.e-tax.nta.go.jp

一括処理に困るのだろう。

 

vimで開いてホールドバッファ機能で無理やり直すという手はある。

:%s/\("[0-9]\{3},[0-9]\{3},[0-9]\{3} "\),/\1,/g

 

これだとなんか違う。

f:id:min117:20201108235358p:plain

数字カンマで区切られた3桁ごとにホールドしないと。

 

こうか。

:%s/\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3",/g

 

これだとうまくいくけど

f:id:min117:20201109000044p:plain

例えば16行目の "1,119,000,000 " は直せていない(金額カンマの数が多いから)

f:id:min117:20201108235834p:plain

数字のパターン分、手作業で繰り返してやるとうまくいく。

:%s/\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

:%s/\"\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

:%s/\"\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

 

f:id:min117:20201109000618p:plain

が、手間が多いし間違いのモト。なかなかに面倒くさい。

min117.hatenablog.com

min117.hatenablog.com

 

 

一番てっとり早く直すには、いったんExcel開いて「タブ区切りのCSV」として保存してから、金額のカンマを取り去るのが一番早いだろう。

 

しかしExcelを使うとなんか負けた気がする。シェル(bashawk)またはPython3でやりたい

 

まずはPython3で試す。pandasを使う。

>>> import pandas as pd

>>> 

>>> pytable = pd.read_csv('Sheet1a.csv', encoding="utf8")

>>> 

>>> pytable.head()

>>> 

>>> pytable.tail()

 

csvファイルをpandasのデータフレームとして読み込んだ

f:id:min117:20201108222518p:plain

これの「営業利益」列だけ、金額のカンマを取り去ってみる。

 

str.replaceメソッドを使うとできるようだ(データフレームのメソッドか?)

>>> pytable.営業利益.str.replace(',', '')

 

f:id:min117:20201108222944p:plain

note.nkmk.me

 

で、これを全ての金額列にやるにはどうするんだ?

 

for文で回せばいいように思えるが、金額ではない列もあるから、「もし金額列だったら」または「列の訂正が NNN,NNN,NNNだったら」というif文が必要になる。

 

bashで(やりたいコードの)イメージを書くとこんな感じ。

f:id:min117:20201108223547p:plain

これをPython3でやる必要がある。Python3でのfor文での回し方と、if文での正規表現のしかたを調べないといけない。あとでやる。

 

課題

・そもそもpandasのデータフレームなんだから、各列が数値データか文字列かを判定して「数値だったら一括して文字列の置き換え(金額カンマを無くす)」のような機能が(データフレームの)関数なりメソッドとしてありそうなものだ。調べる。

qiita.com

 

 

・財務諸表の金額はバカでかい。売上高で "116,861,000,000円 "てことは116億円てことだ(新自由主義が闊歩して大企業は好きなように儲けてやがる。国民は非正規ばかりで貧しくなっているわけだが)。

先ほど読み込んだデータフレームで売上高の列を表示してみると

>>> pytable.売上高

 

f:id:min117:20201108224629p:plain

全ての行で金額のカンマの数は3個ある(例えば8の行でもカンマは3個ある)。

 

営業利益の列はこう。

>>> pytable.営業利益

 

f:id:min117:20201108224713p:plain

こちらは、例えば8の行では金額のカンマは2個しかない。

つまり、列によって金額を区切るカンマが3個だったり2個だったりする。

 

先ほどの

>>> pytable.純利益.str.replace(',', '')

を使えば、3個だろうが2個だろうが一気に置き換えしてくれるけど、仮にこれを正規表現で実装しようとしたら、どうやればいいのか?

bashでやるとしたらイメージはこんな感じ?

f:id:min117:20201108225350p:plain

ダメやな。カンマの数が不定だからこれだとキャプチャできない。これも調べる必要ある。

 

CSVはシンプルだけど奥深い。実務で一番使うからだと思う。手に覚えさせて一瞬で処理できるようにしたい。

www.yoheim.net

teratail.com

gappyfacets.com

 

 ※ vimのホールドバッファ機能で数字列のカンマを全部取り去るとこうなった。

f:id:min117:20201109003552p:plain

かなり苦労はある。

 

: 17 w Sheet1b.csv

: 18 q!

: 19 %s/\"-\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 20 %s/\"-\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 21 %s/\"-\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 22 %s/\"-\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 23 %s/\"-\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 24 %s/\"-\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 25 %s/\"\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 26 %s/\"\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 27 %s/\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 28 k

: 29 %s/\"\([0-9]\{1}\),([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 30 %s/\"\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 31 %s/\"\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 32 %s/\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4",/g

: 33 %s/\"\([0-9]\{1}\),\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4\5",/g

: 34 %s/\"\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4\5",/g

: 35 %s/\"\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4\5",/g

: 36 %s/\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3\4\5",/g

: 37 w

: 38 %s/\"\([0-9]\{1}\),\([0-9]\{3}\).\([0-9]\{2}\) ",/"\1\2.\3",/g

: 39 %s/\"\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3",/g

: 40 %s/\"\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3",/g

: 41 %s/\"\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"\1\2\3",/g

: 42 %s/\"-\([0-9]\{1}\),\([0-9]\{3}\).\([0-9]\{2}\) ",/"-\1\2.\3",/g

: 43 %s/\"-\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"-\1\2\3\4",/g

: 44 %s/\"-\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"-\1\2\3\4",/g

: 45 %s/\"-\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"-\1\2\3\4",/g

: 46 %s/\"-\([0-9]\{3}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"-\1\2\3",/g

: 47 %s/\"-\([0-9]\{2}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"-\1\2\3",/g

: 48 %s/\"-\([0-9]\{1}\),\([0-9]\{3}\),\([0-9]\{3}\) ",/"-\1\2\3",/g

 

しかし、カンマさえ消えればあとはawkでも扱える。例えばこう。

f:id:min117:20201109003902p:plain

$ cat Sheet1b.csv | awk -F, 'BEGIN{OFS=","}{print $2,$10,$11}' | more

 

f:id:min117:20201109004042p:plain

やっぱbashawkが一番便利で最高だなぁ。

Python3で統計量(describeメソッド)もしてみる。

>>> pytable = pd.read_csv('Sheet1b.csv', encoding="utf8")

>>> 

>>> pytable.describe()

 

これは正しく動いた気がする。

f:id:min117:20201109004237p:plain

countが58997で、bashの cat | wc と同じ数字だし。

f:id:min117:20201109004357p:plain

いいぞ。手が覚えてきた。