この記事についての注意
この記事は、下記のブログにリライト中なのでできればそちらを見ていただければと思います。
前処理とは?
蓄積したデータを、目的の作業(機械学習など)をするために、綺麗にしたり、加工したりすること。
機械学習を行う方々は、データ収集作業とあわせて この作業に1番の作業時間を使っているらしい。
前処理を学習する上で登場する言葉
- データクレンジング
- データクリーニングと同義。多分「前処理」の中の一部の作業を指す。
- データクリーニング
- データクレンジングと同義。
- データラングリング
- 上の2つよりは広義。多分「前処理と同義」か、「前処理を内包」している。
前処理のフロー
下記のようなことを行う。もっと効率的な順序があるかもしれないし、もっと細かい作業もある。
-
事前分析
- データの情報量、状態などをチェックする。
-
クリーニング
- 列名の変更
- 表記揺らぎのチェック
- 重複行のチェック
- 欠損値のチェック
- 不要列の削除
-
加工・変換・構成変更
- 列の追加
- 他のデータをマージ
- 特徴量の追加
テスト環境を準備しよう。
学習用にPythonテスト環境を用意しよう。下記のいずれかの方法がある。
ネット環境の場合
- Google Colaboratoryが無料でめちゃくちゃ便利。
- 重い処理でも結果をGoogleDriveにファイルダンプする処理を書いておけば、一旦ブラウザやPCを閉じても処理が終わったころにダンプファイルを確認すれば良い。
▲Google colaboratoryの使い方や初期設定方法はコチラの記事でまとめています。
ローカル環境の場合
- 自身のPCにPython入れる。
- 社内サーバなどにPythonとJupyterNotebook入れてブラウザからリモートアクセスを許可する設定を行うと、Python入ってないPCでpython使えるし、Pythonわからないチームメンバに簡単な説明でちょっとしたスクリプトを配布できて色々捗る。
- リモートアクセス許可設定方法は こちら などを参考にするとよい。
- PythonとJupyterNotebookを1からインストールする場合はAnacondaパッケージで一緒くたにインストールするのが楽だし公式の推奨です。
テストデータを作ろう
テストデータを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
- 準備中
事前分析 に使う構文・メソッド
#最大値、最小値、平均値、標準偏差などの参照
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()]
欠損値の削除
# dfに1個でも欠損値があったら行を削除する
df.dropna(axis = 0, how = 'any', inplace=True)
# 閾値を決めてドロップする(欠損した列を3列以上もつ行を削除する)
# inplaceしないとソースデータに反映されない。
df.dropna(thresh=3,inplace=True)
欠損値の穴埋め
fillna
df.loc[df["単価"].isna(),"単価"] = 0
欠損値を無視して計算する
# 計算の際に少しでも欠損値があると結果Nanになる。欠損値を無視するオプションを使う。
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)]
列の追加、特徴量追加に使う構文・メソッド
関数を使って列を追加
dftest["grade"] = dftest["品名"].apply(lambda x : x[0:2] )
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
apply関数を使って列を追加
#下記の関数を作成する。
##引数の値に“限定”が含まれていたら“限定製品”という文字列を返し、
##引数の値に“新”が含まれていたら“新製品”という文字列を返し、
##引数の値がそれ以外だったら“na”という文字列を返す。
def newitem(x):
if "限定" in str(x):
return "限定製品"
elif "新" in str(x):
return "新製品"
else:
return "na"
#作成した関数で品名列を評価しながら、新しい列“製品カテゴリ”を作成して結果を返す。
df["製品カテゴリ"] = df["品名"].apply(newitem)
列名・行名(インデックス)
# 列名の変更 (辞書形式で変更する)
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+|^[0-9][0-9]?/[0-9]*)", 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]]
特徴量とは?
特徴量=機械学習の予測モデルを作る上で、直接与える列。
予測モデルを作る際に、集めたデータの中ですべての列が予測モデルに必要なわけではない。また、列が不足している可能性もある。
機械学習ではこの列の選別をしたり、新しい列を追加して特徴量を増やしたりすることが重要で、特徴量列を増やすことを、特徴量作成という。
特徴量を作成したりチューニングしたりすることをFeature Engeneering
という。
データを出力する(Excelで書き出す)
▲Google CoraboratoryならPythonのデータをGoogleドライブ上にExcelとして書き出すことも簡単です。上書きもできちゃいます。上の記事でOpenPyXLを使ったExcel出力についてまとめています。
データを出力する(スプレッドシートで書き出す)
▲Google CoraboratoryならPythonのデータをGoogleドライブ上のスプレッドシートへの書き出すことも簡単です。上の記事でgspreadを使ったGoogleスプレッドシートへの出力についてまとめています。
おわり
以上、Pandasを使ったデータ分析の基礎でした。