Python 実験データ整理プログラム[ファイル探索]


f:id:wataru_boss:20180625222708p:plain

こんにちは。BOSSです。 実験データ整理プログラムに進展があったので、今日はそのことについて残していこうかと思います。

前回まで

前回の内容はこちらを参照してください。

Pythonで実験データ整理プログラム[CSVの出力] - 機械学習はじめました。

簡単に振り返ると、前回までとしては

  • CSVファイルをヘッダー部以外を読み込むことができた

  • 名前を変えて同一ディレクトリー内に保存することができた

というところまで機能を持たせることができました。

今回の目標

ただし前回までのプログラムは1ファイルの入力に対して1ファイルの出力という形でありました。

実際の実験データはファイルが複数個あることが多く、現状では効率が悪いですしプログラムを書く意味がありません。

そういうこともあって、今回の目標は以下のように設定しました。

  • 特定ディレクトリーを読み込み、CSVファイルを探す

  • 見つけたCSVファイルに前回までと同じくヘッダー部以外を別ファイルに書き出す。この時入力とは別ディレクトリーに保存する

つまり、 f:id:wataru_boss:20180606231422p:plain

こういうディレクトリーを読み込んで f:id:wataru_boss:20180606232009p:plain こういうファイルの中でデータ部分、つまり15行目以降のみのファイルを別ディレクトリーに保存するということです。

ファイル探索機構

osモジュールを用いたファイルツリー取得

以前にもPythonの基本としてまとめて部分になります。詳細は以下を参照してください。

リンクはる

import os

def directory_tree(target_path):
    for root, directories, files in os.walk(target_path):

記述内容的にはこんな感じ。これを用いてファイルを探し、ファイルひとつひとつに対してファイル書き出しを行おうというのが基本になります。

ファイルパス探索部のプログラム

CSVファイルの読み込みにはファイルパスが必要になります。

したがって、指定ディレクトリー内のファイルのパスをひとつずつ取り出すプログラムを考えました。

こんな感じです。

class get_path:     #引数にディレクトリーパス 戻り値はディレクトリー内のCSVファイルのファイルパス

    def __init__(self, input_directory_path,):
        self.input_directory_path = input_directory_path

    def get_file_path(self):

        file_dict = {}
        for root, directories, files in os.walk(self.input_directory_path):
            for file in files:
                file_path = os.path.join(root, file)
                file_dict[file] = file_path


        return file_path_dict

クラス名はget_pathとし、実験データが入っているinputディレクトリーのパスを引数にとります。

そしてget_file_pathメソッドではosモジュールを用いてファイルの探索をして、ファイルパスが格納されたリストとファイル名が格納されたタプルを戻り値としています。

ですのでfile_path_dictという辞書を定義し、keyはファイル名、valueはos.path.joinメソッドで結合したファイルパスとしています。

このあたりまではファイルツリー取得でやった内容とそう大差はありません。

ちょっとした誤算

本来ならばディレクトリー内にCSVファイルだけ入れておけばそんなにややこしくないと思っていました。

しかし、ここで実は隠しファイルがあるということに気がつきました。それが
.DS_Store
というファイル。

そこで拡張子で処理を行うかどうかという判定をつけることにしました。そのためにファイルの拡張子とそれ以外の部分で分けておきます。

そのための記述が以下の部分。

base, ext = os.path.splitext(file)

このようにしておくことで、変数extに拡張子が格納されます。

そして、拡張子で判定し、辞書に追加します。

if ext in ['.csv', '.CSV']: 
    file_path_dic[file] = file_path

拡張子は小文字と大文字共に認識されると思うので、in演算子を用いて.csvと.CSVの二つに対応できるようにしています。

実装

色々工夫して考えてみた上記の内容を実装してみました。 どうしても必要な情報は以下の通り。

  • 元データがあるディレクトリーパス

  • outputするディレクトリーパス

  • CSVファイル読み込み時のスキップ行数

これらを指定して、各クラスに情報を渡しています。

プログラム全体としてはこのような感じ。

import pandas as pd
import os


class get_path:

    def __init__(self, input_directory_path):
        self.input_directory_path = input_directory_path  # inputディレクトリーのパス

    # inputディレクトリー内のCSVファイルを探索、ファイルパスとファイル名をそれぞれリストに格納
    def get_file_path(self):

        file_dict = {}
        for root, directories, files in os.walk(self.input_directory_path):
            for file in files:
                base, ext = os.path.splitext(file)
                file_path = os.path.join(root, file)

                if ext in ['.csv', '.CSV']:  # どうも比較演算子はin演算子を使うのが良いみたい。or とか|で区切るとなんかエラー出た。
                    file_dict[file] = file_path

        return file_dict

class new_csv:

    def __init__(self, file_dict, output_directory_path, rows):
        self.file_dict = file_dict  # inputディレクトリー内のファイルパスを格納したリスト
        self.output_directory_path = output_directory_path  # outputディレクトリーのパス
        self.rows = rows  # スキップしたい任意のヘッダー行数

    # 任意の行数のヘッダーを取り除いてoutputディレクトリーにファイルを作成する
    def cut_header(self):

        for file_name, file_path in self.file_dict.items():
            # ファイルを開いてデータフレームを作成
            data_pd = pd.read_csv(file_path, skiprows=self.rows, encoding="Shift_jis")
            # アウトプット用にファイル名を変更
            output_file_name = file_name[0:-4]
            output_path = os.path.join(self.output_directory_path, output_file_name + '_output.CSV')

            data_pd.to_csv(output_path, encoding="Shift_jis", index=False)

        return


def main():
    print('Enter the Input Directory Path\n')
    input_directory_path = input()  # 元CSVデータが入ってるディレクトリーパスを入力
    print('Enter the Output Directory Path\n')
    output_directory_path = input()  # 出力先ディレクトリーパスの入力
    print('Enter skip rows\n')
    rows = int(input())  # スキップするヘッダー行数を指定

    # get_pathクラスのget_file_path関数を実行
    # ディレクトリー内のCSVを検索、パスとファイル名をそれぞれ別のリストに格納して返ってくる
    directory = get_path(input_directory_path)
    file_dict = directory.get_file_path()

    # new_csvクラスのcut_header関数を実行
    # パスリストを引数に各ファイルについてヘッダー部の削除、outputディレクトリーに名前を変更して保存
    file = new_csv(file_dict, output_directory_path, rows)
    file.cut_header()


if __name__ == '__main__':
    main()

プログラムの動き

どういう動きをするか見て行きます。

ユーザーが行うのは情報のみ。こんな感じです。 f:id:wataru_boss:20180606231055p:plain

  • 読み込むCSVファイルが入ったディレクトリーパスを指定

f:id:wataru_boss:20180606231422p:plain 再掲しますが、こういういくつもファイルがあるという想定です。

  • 出力先ディレクトリーパスを指定

  • スキップ行数を指定

今回は14行スキップします。

すると、指定したOutputディレクトリーには f:id:wataru_boss:20180606232536p:plain このようにものの一瞬で新しいCSVファイルが作成されました。

CSVファイルの中身を見てみると f:id:wataru_boss:20180606232737p:plain

しっかりとデータだけ取り出せています。

狙った挙動を示すプログラムを書くことが出来ました!

まとめ

実際作ってる過程でかなりたくさんエラーを吐き出していて、これだけの内容でも私には大変でした。

ファイル操作、pandasの基本操作。 何もかも初めてでひとつずつググりながらエラーと戦いました。

なんとか動くとができてよかったです。

今回示している例も全て私が大学院の研究室で行なっている実験のデータです。

実験データはtab区切りのtxtファイルとするというフォーマットが指定されています。それも各列のインデントが揃うようにしないといけません。

ですので次はtab区切りのtxtファイル出力を目指していきたいと思います。