techno_memo

個人用の技術メモ。python・ROS・AI系のソフトウェア・ツールなどの情報を記載

前処理② 数値データに対する前処理 (データの選定・欠損値・不正値の補間・上下限ガード)

やりたいこと

 下記のような数値データに前処理を実施して、解析に適する形式に修正したい

 (統計処理ではなく、センサーでの値取得においてよくあるデータ取得時の不備などを想定)

  • データの選定:大量のデータ項目から必要なものだけを選定する

  • 欠損値:データがない、正しい形式で取得できていない (数値を期待しているのに文字列が含まれるなど)

  • 上下限ガード:信号の上下限や前後の信号の値に対して、異常な数値となるデータを取り除きたい

f:id:sd08419ttic:20190320231623p:plain

実装

データの選定

大量に選択したデータから必要なものだけを選定したい。 どのデータが必要であるかは、データ項目名をユーザーが文字列のリストとして設定する。

実装方法は、①データ読み込み時に対象とするデータ列名を設定、②DataFrameから特定の列名のみ残す の2種類ある。

データ読み込み時に指定する場合、読み込み時間・読み込み後のメモリ削減にも効果的である。

#数値データの前処理用クラス
class Class_Wave_PreProcessing():
    '''
    各種波形データに前処理をするクラス
    '''
    def __init__(self):
        '''
        コンストラクタ
        '''
        pass

    def get_only_required_columns(self,df_arg,req_columns):
        '''
        pandas dataframeからユーザーが指定取得対象とする列のみを取得する関数
        df_arg:データフレーム入力値、req_columns:取得対象とする列の名前を格納したlist
        '''
        df_new = df_arg[req_columns]

        return df_new

if __name__ == '__main__':
    
    #参考としたデータ (kaggle datasetで公開されている車両センサー情報) https://www.kaggle.com/zhaopengyun/driving-data
    input_path = "D:\\WS\\VS_project\\BlogProject\\BlogProject\\data\\101a.csv"
    #引数usecolsで読み込み対象とするデータを選択 (1つでも存在しない場合はエラー)
    test_data = pd.read_csv(input_path, encoding="shift-jis", usecols=["position X", "position Y","position Z","yawAngle","pitchAngle","rollAngle"])

    wave_inst = Class_Wave_PreProcessing()
    #データから特定の列のみを選定する関数
    df_new = wave_inst.get_only_required_columns(test_data,["position X","yawAngle"])

欠損値・不正値処理

データが正しく取得できなかった(値なし、文字列など)を削除 もしくは補間したい。

pandasでは標準でNanの除去処理があるが、これをそのまま使うだけでは誤って文字列が入力された場合に対処できない。

各列について数値変換処理to_numericをかけて数値変換(非数字の場合はNanになる)後に、Nanを除去もしくは補間する処理をすることで不正文字列入力時にも対処する。

Nanを含む行ごと削除する処理は下記のように記述できる。

    def elim_error_data_from_df(self,df_arg):
        '''
        DataFrameから無効値を除去する
        '''

        #列数
        orig_columns = df_arg.columns   #入力したDFの列名
        col_num = len(df_arg.columns)   #入力したDFの列数
        df_new = df_arg.copy()          #出力用のDF

        #数値データできない値をNanに変換する処理
        for indx in range(col_num):
            temp_series = df_arg.iloc[:,indx].copy()
            temp_series =  pd.to_numeric(temp_series, errors='coerce')
            df_new = df_new.drop(orig_columns[indx],axis=1)
            df_new[orig_columns[indx]] = temp_series
            df_new[orig_columns[indx]] = temp_series.astype(float)

        #Nanの除去(列ごと)
        df_new = df_new.dropna(how='any')   #any:1つでもNanが含まれる場合に行ごと除去、all:1行全てNanである場合に行ごと除去

        return df_new 

Nanを含む要素を固定値や前後のデータで補間したい場合は下記のように記述できる

interpolate処理は複雑な補間にも対応している。詳細は下記サイトに解説がある。

note.nkmk.me

    def fill_error_data_from_df(self,df_arg,mode="fixed",fixed_val=0):
        '''
        DataFrameから無効値を補間する mode:方式 ("fixed":固定値代入,fixed_val:固定値(デフォルト:0),"below":下の値,"above":上の値,"interpolate":上下平均)
        '''
        #列数
        orig_columns = df_arg.columns   #入力したDFの列名
        col_num = len(df_arg.columns)   #入力したDFの列数
        df_new = df_arg.copy()          #出力用のDF

        #数値データできない値をNanに変換する処理
        for indx in range(col_num):
            temp_series = df_arg.iloc[:,indx].copy()
            temp_series =  pd.to_numeric(temp_series, errors='coerce')
            df_new = df_new.drop(orig_columns[indx],axis=1)
            df_new[orig_columns[indx]] = temp_series

        if mode== "fixed":          #Nanの穴埋め(固定値)
            df_new = df_new.fillna(fixed_val)
        elif mode== "below":        #Nanの穴埋め(Nanの下にある値を代入) ※一番上のデータがNanの場合はそのまま残るので注意 2つ以上続くと続いた先の値を取得して代入
            df_new = df_new.fillna(method='ffill') 
        elif mode== "above":        #Nanの穴埋め(Nanの上にある値を代入) ※一番下のデータがNanの場合はそのまま残るので注意 2つ以上続くと続いた先の値を取得して代入
            df_new = df_new.fillna(method='bfill')
        else:                       #補間処理(Nanの下にある値を代入) ※一番上or下のデータがNanの場合はそのまま残るので注意
            df_new = df_new.interpolate()
        
        return df_new

上下限ガード

各列について、信号の仕様から取りうる値の上下限範囲が定まっている場合、clip関数を用いてそれ以上の値とならないようにガードをかけることができる。

clip関数の詳細は下記サイトに記載されている。

pandas.pydata.org

DataFrame全体にかける場合と、指定した列のみにかける場合でそれぞれ下記のように記述できる。

    def clip_data_from_df_all(self,df_arg,min_val=None,max_val=None):
        '''
        DataFrameの上下限ガード (全体) min_val:最小値, max_val:最大値
        '''

        df_new = df_arg.clip(lower=min_val,upper  = max_val)

        return df_new

    def clip_data_from_df_col(self,df_arg,col_name=None,min_val=None,max_val=None):
        '''
        DataFrameの上下限ガード (特定の列のみ) col_name:列名, min_val:最小値, max_val:最大値
        '''
        df_new = df_arg

        if col_name is not None:
            temp_series = df_arg[col_name]
            temp_series = temp_series.clip(lower=min_val,upper  = max_val)
            df_new[col_name] = temp_series

        return df_new

実装結果のまとめ

下記サイトに上記を1クラスにまとめた実装・使用例を記載

github.com