この記事はデータの「前処理」で良く使うPandasのメソッドついての解説です。半分備忘録なので見づらいようでしたらすみません。随時更新しています!
データの前処理と特徴量とは?
前処理とは?
取得したデータをそのまま扱えることはまずありませんよね。日付のフォーマットにバラつきがあったら統一したり、「ねこ・ネコ・猫」のような表記揺らぎを統一したり、プログラムがうまく理解できるように、プログラムに渡す前に綺麗にしてあげる作業がデータ前処理です。
特徴量とは?
機械学習のプログラムに入力データをわたす際に、集めたデータの中ですべての列が必要なわけではありません。列を選別したり不足している列を追加したりしてチューニングを行っていきます。最終的に実際に入力値となる列データのことを特徴量といいます。特徴量を作成したり選別したりすることを特徴量エンジニアリング(Feature Engeneering)
といいます。
機械学習を行う方々は、データ収集作業とあわせて データ前処理の作業時間に全体の作業時間の7~8割を使うといわれていて大変重要な作業です。
前処理を学習する上で登場する言葉を知っておこう。
データ前処理を学習していると、データ前処理を表す言葉として下記の言葉がでてきます。すべて同じ前処理を表す言葉です。混乱しないようにしましょう!
データ分析に関連する言葉
- データクレンジング[Data cleansing]
- データをきれいにする。
- データをきれいにする。
- データクリーニング[Data Cleaning]
- データをきれいにする。
- データをきれいにする。
- データラングリング[Data Wrangling]
- 「ラングリング」とはもともと馬や牛を集めて飼いならすという意味があるようです。上の2つよりもう少しあいまいな表現なのかもしれません。
前処理の主なフロー
前処理は下記のような流れで行います。これは最終的にどういうカタチのデータが欲しいのかによってさまざまです。
- 事前分析
- データの情報量、状態などをチェックして、どういう前処理の作業が必要なのかを検討する。
- データの情報量、状態などをチェックして、どういう前処理の作業が必要なのかを検討する。
- クリーニング
- 列名の変更
- 表記揺らぎのチェック
- 重複行のチェック
- 欠損値のチェック
- 不要列の削除
- 加工・変換・構成変更
- 列の追加
- 他のデータをマージ
- 特徴量の追加
テスト環境の準備
学習用にPythonテスト環境を用意しよう。下記のいずれかの方法がある。
- ネット環境
- Google Colaboratoryが無料で凄いです。
- 重い処理でも結果をGoogleDriveにファイルダンプする処理を書いておけば、一旦ブラウザやPCを閉じても処理が終わったころにダンプファイルを確認すれば良い。
- GoogleDrive X Colab連携方法は過去のQiita記事参照(Google colaboでのGoogle Driveをマウントして操作する)
- ローカル環境
- 自身のPCにPython入れます。
- 社内サーバなどにPythonとJupyterNotebook入れてブラウザからリモートアクセスを許可する設定を行うと、Python環境が整っていないPCでpythonを使えることができる、Pythonわからないチームメンバに簡単な説明でちょっとしたスクリプトを配布できて色々捗ります。
- リモートアクセス許可設定方法は こちら などを参考にすると良いです。
テストデータを準備しよう!
テストデータをseriesでもDataframeでもサクッと用意できるようになろう。
- Seriesのテストデータ作成例
import numpy as np from pandas import DataFrame,Series # 開始値1~終了値100の値をランダムに10個持つSeriesを作成する。 series1 = Series(np.random.randint(1,100,10)) # 出力結果 print(series1) index | data 0 90 1 52 2 33 3 98 4 27 5 42 6 80 7 16 8 47 9 45 dtype: int32
- Dataframeのテストデータ作成例
import numpy as np from pandas import DataFrame,Series df = pandas.DataFrame( { "id": range(1,11), "data1" : np.random.randint(1,100,10), "data2" : np.random.randint(100,500,10), } ) # 出力結果 print(df) id data1 data2 0 1 19 394 1 2 90 110 2 3 67 273 3 4 33 355 4 5 51 141 5 6 81 331 6 7 57 110 7 8 26 460 8 9 67 201 9 10 70 120
Pandasメソッド
事前分析をするコマンド
#最大値、最小値、平均値、標準偏差などの参照 df.describe() #各列のデータ型の参照 df.info() #データの先頭数行を閲覧する df.head() #データの末尾数行を閲覧する df.tail() #データの次元数(何行、何列あるか) df.shape # 値の個数 df["column"].value_counts() # 値の個数(上と同じ) pd.value_counts(df["column"]) # 列名の一覧① df.columns # 列名の一覧② df.count()
欠損値を確認する
# 欠損値のカウント("行数 - データの個数"にて求める場合) # 全体の列の欠損状況を一覧できる。 df_s.shape[0] - df_s.count() # 欠損値のカウント(特定の列) df_s["不良内容.1"].isnull().value_counts() # 欠損値がある行を表示(特定の列に) df_s.loc[df_s["不良内容.1"].isnull()]
欠損値を削除する
行(あるいは列)に欠損値がある場合、その行(あるいは列)をまるっと削除したい時があります。例えばデータの途中に完全な空白行を含むExcelファイルを読み込んだ時です。そんな時にdropna()がとても便利です。
how=引数に設定できる値 | |
---|---|
all | 行あるいは列のすべてのデータが空白(Nan)のとき。 |
any | 行あるいは列にひとつでも空白(Nan)があるとき。 |
# dfの各行に1個でも欠損値があったら行を削除する df.dropna(axis = 0, how = 'any', inplace=True) # 閾値を決めてドロップする(欠損値を3列以上もつ行を削除する) # inplaceしないとソースデータに変更は反映されない。 df.dropna(thresh=3,inplace=True)
欠損値を穴埋めする
データフレームの欠損値の穴埋めはfillna()を使うことができます。
fillnaの使用例 | 説明 |
---|---|
df.fillna(method=’ffile’) | 前方の値で穴埋めする。 |
df.fillna(method=’bfile’) | 後方の値で穴埋めする。 |
df.fillna(0) | すべての空白セルに0を入力する。 |
# 1列目の欠損値を穴埋めする。上の値で穴埋め df.fillna(method="ffill", inplace=True)
欠損値を無視して演算する。
# 計算の際に少しでも欠損値があると結果Nanになる。欠損値を無視して計算するにはskipnaオプションを使う。 df.sum(skipna=False) df.mean(skipna=False)
重複を確認する
df_s.duplicated().value_counts() df_s[df_s.duplicated() == True].count() # 列単位 df_s[df_s.duplicated() == True ]["部品"].count()
重複を削除する
適切なデータ型になっているかチェックし、必要があれば適切な型に変換する。数値型に変換する場合には、事前に文字列を数値にしておいたり、Nullデータを0にしておいたり、といった作業も覚えておく必要がある。astype
メソッドを主に使う。
#データ型の参照 df2.info() #データ型の変換(新しい列の追加にて実現) df["品番2"] = df["品番"].astype(object) #データ型の変換(元の列にそのまま代入して変換) df_s["test"] = df_s["test"].astype(object) # object(string) df_s["test"] = df_s["test"].astype(str) # object(string) df_s["test"] = df_s["test"].astype(float) # Float df_s["test"] = df_s["test"].astype(int) # integer # 型違いの値を強制的に排除(To Nan)しつつ数値型に変換 df_s["test"] = pd.to_numeric(df_s["test"] , errors="coerce")
データを抽出する・検索する
#特定の列の抽出(元データの変更はしない) df[["colB","colD"]] #正規表現を使って抽出する tanka_nan = df["単価"].str.extract("(^\D*)", expand=False) tanka_nan.value_counts() #「[単価列の値が"バルク品"]の行抽出」且つ 「単価列の列抽出」で抽出 df.loc[df["単価"] == "バルク品","単価"] # リストから行フィルタし、新しいDFを作る df2 = df[df["品番"].isin(mylist)]
列を追加する(apply関数)
dftest["grade"] = dftest["品名"].apply(lambda x : x[0:2] )
列を追加する(apply関数2)
#下記の関数を作成する。 ##引数の値に“限定”が含まれていたら“限定製品”という文字列を返し、 ##引数の値に“新”が含まれていたら“新製品”という文字列を返し、 ##引数の値がそれ以外だったら“na”という文字列を返す。 def newitem(x): if "限定" in str(x): return "限定製品" elif "新" in str(x): return "新製品" else: return "na" #作成した関数で品名列を評価しながら、新しい列“製品カテゴリ”を作成して結果を返す。 df["製品カテゴリ"] = df["品名"].apply(newitem)
列を追加する(map関数)
dftest = pd.DataFrame({"prefecture":["hokkaidou","shizuoka","okinawa"],"city":["sapporo","shizuoka","naha"]}) dftest_map = {"hokkaidou":10,"shizuoka":20,"okinawa":30} dftest["maptest"] = dftest.prefecture.map(dftest_map) dftest
列名・行名(インデックス)を操作する
# 列名の変更 (辞書形式で変更する) df = df.rename(columns={'品 名':'品名'}) # 列名の変更(一番最初の列を変更する) df.rename(columns={df.columns[0]: "品名"})
列へ代入する
df.loc[:,"単価"] = 0
列へ代入する(特定の列を抽出して)
df.loc[df["単価"] == "支給", "単価"] = 0
正規表現で列を追加
df["grade"] = df["品名"].str.extract( "(^[A-Z]{2}|^[A-Z]{2}|^/d/d?//d+|^\d\d?/\d*)", expand=False)
グループ化 に使う構文・メソッド
グループはgroupbyオブジェクトを使う
df.groupby()
を使う。- グループ化して変数に入れると変数にはGroupbyオブジェクトが入る。(DataFraem,Seriesではない)
df.groupby('items').get_group('item_1') g1 = df.groupby('items') print(g1.groups) df.groupby('items').size() # まとめて集約 df_s.groupby("袋詰場所").agg(np.sum) # 辞書を使った集約 df_s.groupby("袋詰場所").agg({"総生産数":np.sum, "ロット":"first" })
Lambda関数による追加
train["year"]= train["datetime"].apply(lambda x : x.split("-")[0] )
縦持ちを横持ちに変換
- melt(融解という意味)
- pivot
準備中
横持ちを縦持ちに変換
準備中
ビニング する
非連続値を、ビンというものを使って より少ない数で分割してグループ分けすること。ビン分割ともいう。Binは棚という意味があります。
例えば人の年齢が入っている列があったとして、それを[10代、20代、30代・・・]といった年代ごとに区間でグループ分けするように使います。
cut
、qcut
を使う。
作成した”棚”に元データ(今回は年齢データ)を仕分けるイメージ。
当然だが、ビンの数はソースとなるデータより少なくなる。
#まずこういう年齢データがあったとする。 ages = [random.randint(0, 100) for i in range(20)] print(ages) #[84, 73, 27, 85, 8, 17, 46, 16, 95, 62, 38, 47, 63, 44, 69, 26, 0, 40, 34, 79] #この年齢データをグループ分けするためのBinsを用意する bins = np.arange(0,90,10) #連番を生成 print(bin) # >>> [ 0 10 20 30 40 50 60 70 80] #カットする cats = pd.cut(ages, bins) print(cats) # [NaN, (70.0, 80.0], (20.0, 30.0], NaN, (0.0, 10.0], ..., (20.0, 30.0], NaN, (30.0, 40.0], (30.0, 40.0], (70.0, 80.0]] # Length: 20 # Categories (8, interval[int64]): [(0, 10] < (10, 20] < (20, 30] < (30, 40] < (40, 50] < (50, 60] < (60, 70] < (70, 80]]
関連記事
Pythonの実行結果をGoogleスプレッドシートに書き出す。
Pythonの実行結果をGoogleドライブ上のExcelファイルに書き出す。
Pandasでの複数のExcelファイルをマージする例
Pythonを学ぶ方法
オンラインスクールはこちら
先着1000名まで1万円引きキャンペーン実施中!(8月31日まで)
\買い切りだからコスパ最高・永久にユーザーコミュニティ参加可能/
書籍はこちら
▲前処理系の書籍を7冊購入しました。(Amazon kindle Unlimited含む)
最初に購入するなら基本を網羅的に解説しているオライリーがおすすめです。
▲プロ目線の解説が面白かった1冊です。余裕があれば見てみることをオススメします。文字での踏み込んだ解説が多いので初心者向けではありません。