固定長のファイルをpythonでごりごりCSV化した話。

# 0. 前提
- やったこと：固定長DATファイルの中身を調べるためにpythonでCSVにした．
- 本文の目的：記録．
- 実行環境：macOS Mojave (version 10.14.6) / shell: [fish](https://fishshell.com/) / Eclipse 2017 (version 4.7.3) / python3 (version 3.7.4)
    - Ecpliseでpythonを使う方法は[こちら](http://ktykwsk.com/eclipse%E3%81%A7python%E7%94%A8ide%E3%82%92%E6%A7%8B%E7%AF%89%E3%81%99%E3%82%8B/)を参考にしました．

# 1. 固定長DATファイルをtxtファイルにコピーする
ここはターミナルでコマンドを使って、対応しました．
ターミナルを開いて、catコマンドを実行

```shell:
cat sample.DAT > sample.txt
```
エラーがでなければ、これでDATファイルはtxtファイルへ変換されました．
catコマンドはファイルの中を閲覧するコマンドです．
つまり，ここではcatコマンドでsample.DATの中を出力し，その結果をsample.txtとして保存しています．
詳細は[こちらのサイト](https://eng-entrance.com/linux_command_cat)へ．

※追記(2019/10/12)
ご指摘いただいたので，追記します．
1ファイルであれば，ただコピーするだけなのでcpコマンドで実行できます．(こっちのほうが自然．．．
複数ファイルを結合する場合には上記方法で，実行できます．

```python:
cp sample.DAT sample.txt
cat sample1.DAT sample2.DAT > sample.txt
```


# 2. txtファイルをCSV化する

ここからが本番です．

## 2.0. やりたいこと

現時点でのtxtファイルはこんな感じです．

```:sample.txt
2018100400400010234
2018100400050020234
2018100500700010022
```
これをこんな感じにしたいのです．

```:sample.csv
購入日,購入金額,行数,顧客番号
20181004,004000,1,0234
20181004,000500,2,0234
20181005,007000,1,0022
```

なので，pythonでやりたいことはこんな感じになります．
1. txtファイルを１行ずつ読み込む
2. 読み込んだデータを１行ずつ分割する
3. csvとして出力する

## 2.1. txtファイルを読み込む関数の作成
pandas使うのでインポートします．

```python:pandasインポート
import pandas as pd
```

今回は、複数ファイルをじゃんじゃんやるので関数化しています。
対象ファイルのパスを渡したら、そのファイルを１行ごと読み込んだリストを返します．

```python:txtファイル読み込み
def read_file(path):  # ファイルのパスを引数として渡す
    f = open(path, encoding='utf-8')  # ファイルを読み込む
    line = f.readlines()  # ファイルを1行ずつリスト化する
    f.close()
    return line
```

さて、これで1行ずつリストの要素として格納することができました．
返ってきた値はこうなります．

```:リスト(1段階目)
['2018100400400010234\n', '2018100400050020234\n', '2018100500700010022']
```

改行コードが入ってしまっていますが、今回は問題ないのでこのままにします．
回避方法は[こちら](https://note.nkmk.me/python-file-io-open-with/)の記事に書いてあります．

## 2.2. １行ずつ分割する関数の作成

さて，１行ずつ分割をして意味のある値に戻していきます．
2.1.で作成した関数の返り値のリストには，txtファイルの１行が文字列として格納されています．
そのため，さきほどのリストの要素を一つずつ読み込んで，それをスライスで分割してリストに格納します．

```python:1行を分割
def split_line(l):
   l_split = l[[0:8], [8:14], [14,15], [15:19]]
   return l_split
```
pythonでは，文字列もリストとして扱えるので，要素数を指定することで，
文字の一部を抽出することができます．

例えば，1行目の`'2018100400400010234\n'`を上記の`split_line`関数に渡すと
返り値はこのようになります．

```:リスト(2段階目)
['20181004', '004000', '1', '0234']
```
(ヨダン)
ぶっちゃけ区切りを指定しなきゃいけないのはだるいですが，これが最善でした．．．
どなたかもっと良い方法があれば，ご教授くださいませ．

## 2.3. CSVとして出力する
 
`split_line`関数を`read_file`関数から得たリスト(1段階目)の要素すべてにかけなければなりません．
for文でも一応できますが，数万行あると恐ろしい計算時間になってくるので，map関数を使っていきます．
(pythonの各関数の処理時間を比較している記事は多数あるので，ぜひ参照してみてください．[例](http://sionff.hatenablog.jp/entry/2017/12/29/063000))

リスト(1段階目)の各要素を`split_line`関数に渡して実行するため，下記のような処理が必要です．

```python:map関数
line = ['2018100400400010234\n', '2018100400050020234\n', '2018100500700010022']
map(split_line, line)
```

しかし，map関数の返り値はmapオブジェクトなのでそのままでは扱いにくいです．
そのため，今回はそのままDataFrame化してしまいます．
したがって，下記のような関数にリスト(1段階目)を渡して，全行を分割してDataFrame化します．
ここでまとまてカラム名も入れてしまいます．

```python:全行の分割とデータフレーム化
def to_df(line):
    df = pd.DataFrame(map(split_line, line)  # 全行を分割して，データフレーム化
    df.columns['購入日', '購入金額', '行数', '顧客番号']  # カラム名
    return df
```

`to_df`関数にリスト(1段階目)を渡すと，返り値はこうなります．

```:to_dfの返り値
     購入日    購入金額  行数  顧客番号
0  20181004  004000  1  0234
1  20181004  000500  2  0234
2  20181005  007000  1  0022
```

これを`to_csv`関数でCSVで出力すればミッションクリア！

```python:to_csv
df.to_csv('sample.csv', sep=',', encoding='utf-8')
```

おつかれさまでした．

# 3. まとめ
関数ごとに紹介したので，下記に全体像を示します．

```python:全体
# txtファイルを読み込んで，リスト化する
def read_file(path): 
    f = open(path, encoding='utf-8') 
    line = f.rearlines() 
    f.close()
    return line

# １行を分割する
def split_line(l): 
   l_split = l[[0:8], [8:14], [14,15], [15:19]]
   return l_split

#　リストの全要素を分割し，データフレーム化
def to_df(line):
    df = pd.DataFrame(map(split_line, line) 
    df.columns['購入日', '購入金額', '行数', '顧客番号'] 
    return df

# txtファイルを読み込み，リスト(1段階目)を得る
List_1 = read_file("./sample.txt")

# リスト(1段階目)をデータフレーム化する
df = to_df(List_1)

# csvへ出力
df.to_csv('sample.csv', sep=',', encoding='utf-8')

```

# 4. あとがき
難しいことは何もしていませんが，pythonで文字列データをいじるとっかかりはつかめるような作業でした．