データレコードが文字列として１つにまとめられていて、なおかつデータ間の区切りスペース数が行ごとに異なる汚いcsv.ファイルをPythonで直してみた

<h1>ある日の悩み・・・</h1>
データマインニングの講座を受講していて、csvファイルの扱いが必須なのですが・・・

![terrible csv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440707/a39cb060-40eb-aa66-7353-3f5717bab851.png)

こんな感じでまあ中身が汚いのです。
データ資料がもともと「.csv.pdf」ファイルで変換する必要があったという点が原因かもしれませんが・・・。
この画像を見てわかるように、
+ データレコードが文字列としてまとめられている(column名も同様)
+ データ間の区切りスペース数が行ごとに異なる
+ ところどころ１列目に該当する数値の左に空白が紛れ込んでいる

といった悲惨なデータ構造になっていることがわかります。
当たり前ですが、こういったcsvファイルをいちいち手直しするわけにもいきません。
そこで、手っ取り早くPythonで自動的に整頓してみることにしました。

<h2>CSV整頓プログラム(align_csv.py)</h2>

CSVを整頓してくれる``align_csv.py``を作成する。
考え方として、ターミナルから``python align_csv.py -i "./csv.file"``で整頓したい
csvファイルを指定・整頓を行えるようにする。


まず使用するモジュールと、コマンドライン(ターミナル)から受けた引数を扱えるようにする。

```python
import pandas as pd
import numpy as n
#コマンドライン（ターミナル）から入力された引数を扱えるよう、sysモジュールをインポート
import sys
#空白や余分な記号を置き換えるため、reモジュールをインポート
import re

#unicodeで表示
pd.set_option('display.unicode.east_asian_width', True)

"""
sys.argvによりターミナルでの入力を受け取る
[0]->実行する.pyファイル(ここでは、align_csv.py)
[1]->第1引数
[2]->第二引数　　
"""
csv_link = sys.argv[1] #第１引数で、指定した.csvファイルへのリンクを取得。
```

<span></span>
次にデータフレームのcolumns名（headers）とその値の集合(rows)を整頓するメソッドの作成。

ちなみに下記コードを見て、
「どうしてそんなstrやintに変換してるの？」
「columnsとその値はndarray型として保持されるんだから、``astype()``や``asarray()``を使って
　その配列にある要素を変換しないの？」
という疑問を持たれる方もいるかもしれない。

しかしここで問題となったデータフレームの各行は、ndarray型で、なおかつ要素は空白を含む一つの文字列という扱いになっている。
例：``['46    54    87     67']　(type=ndarray)``
そのため、astype()等のndarray型の変換メソッドで数値に変換しようにも、要素が空白を含有している一つの文字列であるため適用できず、
split()やre.sub()などで配列内の要素を整形しようにも、ndarray型であるため適用できないことがわかる。
故に、下記のようなコードの形式となっている。
（もっと良い書き方があればぜひ教えていただきたいです・・・）

```python

#column名を整えるメソッド
def align_headers(df):

  """
  複数のスペースを、一つのスペースへと置き換える
  df.columns.values->dfのcolumns一覧。ndarrayオブジェクトになっている。
  re.sub()で扱えるように一度strに変換している。
  """
  headers = re.sub(r"\s+", " ", str(df.columns.values))
  
  """正規表現で余分な記号"[", "]", "'"を削除。
     そして残しておいたスペースをsplitでコンマに変換"""
  headers = re.sub(r"[\[\]']+", "", headers).split(" ")
  return headers

#dfのvaluesを整理するメソッド
def align_rows(df):
  new_rows = []
  #受け取ったdfを一度ndarrayに変換。
  rows=np.array(df)
  #末尾に余分な記号が要素として混入しているため削除
  rows = np.delete(rows, len(rows)-1, axis=0)
  # print(rows, type(rows), len(rows))
  for r in rows:
    #strに変換。以下3行はalign_headers()と同じ。
    str_r = str(r)
    str_r = re.sub(r"\s+", " ", str_r)
    str_r = re.sub(r"[\[\]']+", "", str_r).split(" ")
    #split()では配列の各要素がstrとして出力される。
    #こちらでは数値として扱う必要があるため、list(map(int, val))を用いてintへ変換。
    int_r = list(map(int, str_r))
    new_rows.append(int_r)
    # print(r, len(r))
  # print(new_rows, len(new_rows), type(new_rows))
  return new_rows
```

最後に.csvファイルの読み込み・書き込みと各メソッドの実行を行うメソッドの作成。

```python
def align_csv(df_link):
  
""".csvファイルの読み込み。
　　ついでにlambda関数を適用してlstrip()でデータフレームに含まれている
　　改行コード（/x0c）を除去する"""
  df = pd.read_csv(df_link).apply(lambda d: d.str.lstrip())

  headers = np.array(align_headers(df))
  rows = np.array(align_rows(df))
  """整列させた新しいデータフレームを作成"""
  new_df = pd.DataFrame(rows, columns=headers)
  
  """元となった.csvファイルへの書き込み。
　　　to_csvでは、indexに何も指定していないと新たなindexが追加されてしまい、列数が一つ増えてしまう。
     そのため、index=Falseにして、indexを追加しないようにする"""
  new_df.to_csv(df_link, header=True, index=False)
  
  #変換後の.csvファイルの確認
  df_read = pd.read_csv(df_link)
  print(df_read)


align_csv(csv_link)
```

<h2>実行してみると・・・</h2>

```python:python
$ python align_csv.py "./terrible_data.csv"

#変換前　->  
    kokugo        shakai        sugaku        rika
0      30            43            51           63
1      39            21            49           56
2      29            30            23           57
3      95            87            77          100
4      70            71            78           67
..                                             ...
162                              0    8     2    9
163                             
                                45   26   29   24
164                              73   31   43   32
165                              60   85   89   80
166   


#変換後　->
     kokugo  shakai  sugaku  rika
0        30      43      51    63
1        39      21      49    56
2        29      30      23    57
3        95      87      77   100
4        70      71      78    67
..      ...     ...     ...   ...
161      82      78      80    88
162       0       8       2     9
163      45      26      29    24
164      73      31      43    32
165      60      85      89    80



```

<span></span>
一応.CSVファイルを開いて確認。綺麗・・・。
![aligned_csv.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/440707/9b7375e2-8079-c9df-1c1f-34d753bbbcf9.png)

<h2>以上</h2>

ここまでご覧いただきありがとうございました。
レコードに欠損値が含まれている場合、整頓工程を条件ごとで分岐させたい場合など、
まだまだ改善の余地はありますがひとまず使えるものになりました。
まだ慣れていない部分もあるため、もっと効率のいい書き方などあれば教えていただけると幸いです。