LoginSignup
4
6

More than 1 year has passed since last update.

Pythonでのデータの前処理、使えそうな処理をとにかく記録することにした

Last updated at Posted at 2021-09-12

~データ前処理に対する抵抗感を払しょくしてやる!~

はじめに

 
データの前処理までも自動でやってくれるPycaretというライブラリもありますが、やはり、ある程度のデータの前処理はできないとダメだから・・・とにかく使えそうな処理を記録することにしました。
 
 


実行条件など

Google colabで実行
Kaggleのタイタニックデータで実行
・※これ以外でも使えそうという処理はどんどん追加してゆきます

タイタニックのデータセットについて

以下サイト(Kaggle)の「train.csv」を使わせていただいた。

タイタニックデータセットの項目と内容
項目 内容
PassengerId 乗客ユニークID
Survived 生存フラグ(0=死亡,1=生存)
Pclass チケットクラス(1=上級,2=中級,3=下級)
Name 乗客名
Sex 性別(male=男性,female=女性)
Age 年齢
SibSp 同乗している兄弟,配偶者の数
parch 同乗している親,子供の数
ticket チケット番号
fare 料金
cabin 客室番号
Embarked 出港地(C=Cherbourg,Q=Queenstown,S=Southampton)

前処理してみよう!

1. ライブラリのインストールおよびインポート

以前もある記事で描いたが、使用するライブラリをいちいち検討するのは面倒なので、基本以下としています。乱暴ですいません。

ライブラリのインストールおよびインポート
pip install japanize-matplotlib
# Import required libraries 
%matplotlib inline 
import pandas as pd 
import numpy as np 
import matplotlib.pyplot as plt 
import japanize_matplotlib 
import seaborn as sns 
sns.set(font="IPAexGothic")  
from scipy.stats import norm

2. データの読み込み

csvデータの読込み
df=pd.read_csv('train.csv')
df.head()

image.png

3. 基礎データ確認

まず、データのカラム、欠損値有無、データ型を確認しました。

df.info()

image.png

各カラムの欠損数も確認しました。

各カラムの欠損数確認
df.isnull().sum()

Age、Cabin、Embarkedには欠損値があり、何らかの処置が必要なことがわかります。
image.png

その他の基礎データ確認

データ型の確認
df.dtypes
重複データの確認
df.duplicated().any()
基本統計量の確認(ただし数値データのみ)
df.describe()
画面表示できるデータを最大に
pd.set_option('display.max_rows', None)
df['列の名前'].value_counts()

4. データ確認

欠損値があったCabinはどのようなデータなのかについて見てみました。

df['Cabin'].value_counts()

image.png

Cabinの中身(データ)はバラバラですね。
一応グラフ化もしてみましたが、これから特徴や傾向をつかむことはむつかいように思います。

sns.countplot(y="Cabin", data=df)
df.head()

image.png
image.png
↑ こちらはPandas-Profilingでのグラフです。こちらの方がバラバラ感がよくわかります。

散布図行列
pd.scatter_matrix(df)

その他のデータ確認

データを文字列に変換→すべての列の統計量を表示
df.astype('str').describe()
条件抽出
df[(df[‘列名1’] == ‘A’ ) & (df[’列名2‘]>123)]
#列名1がA かつ 列名2が123以上の条件で抽出
クロス集計(質的データ×質的データ)
# 第1引数:縦列、第2引数:横列、[margins=True:合計値カラムの表示]
pd.crosstab(df['列名A'], df['列名B'])
クロス表×統計量比較
train_data =train[['Sex', 'Survived']].groupby(['Sex'])
train_data.describe()

image.png

#グラフでも確認
sns.countplot('Sex', data=train, hue='Survived')

image.png


参考

ここではこれ以上データ確認については述べませんが、以下ライブラリが便利がと思います。

5. データ分析に関連しない列の削除

次に、カテゴリー化できないものや、データ分析に関連しないデータは列ごと削除しました。
ここでは、先ほどのCabinと、objectデータのName,Ticketは(データ分析には活かせそうにないので)列ごと削除することにしました。

# 特定列削除
df.drop("Cabin", axis = 1, inplace = True)
df.head()

image.png

# 特定列削除
df.drop("Name", axis = 1, inplace = True)
df.head()

image.png

# 特定列削除
df.drop("Name", axis = 1, inplace = True)
df.head()

image.png

ずいぶん、すっきりしました。
 

その他の削除

数値の列は残し、文字列だけを削除
df = df.select_dtypes(exclude='object')
column名の変更
df_past = df_past.rename(columns={'Pclass': 'Class'})

 

6. 欠損値の処理

再度、データのカラム、欠損値有無、データ型を確認しました。
項目数は11→8となり、すこしスッキリしました。

image.png

次は、AgeとEmbarkedの欠損値処理を行うことにします。

まずはAgeです。ヒストグラムでこのデータの傾向をみてみます。一山の分布となっています。ピークはすこし左よりの分布となっているように思います。

sns.distplot( 
    df['Age'],bins=10, color='#123456',label='data', 
    kde_kws={'label': 'kde','color':'k'}, 
    fit=norm,fit_kws={'label': 'norm','color':'red'}, 
    rug=False 
) 
plt.legend() 
plt.grid() 
plt.show()

image.png

左よりの分布を考慮し、欠損値には「中央値」を補完しました。

df['Age'].fillna(df['Age'].median(), inplace=True)

再度、データのカラム、欠損値有無、データ型を確認してみます。 Ageの欠損値はなくなりました。

df.info()

image.png

次は、Embarkedです。まず傾向をグラフで見てみました。これは欠損値以外が表示されたものですが、S,C,Q の3パターンのでデータであることがわかります。

sns.countplot(y="Embarked", data=df)

image.png

Embarkedの欠損値処理は、もっとも多い「S」に補完しました。以下infoの通り、補完によって欠損値がなくなったことがわかります。

df["Embarked"] = df["Embarked"].fillna("S")
df.info()

image.png

他の欠損値処理について

実務では、中途半端に欠損値を補ったりせず、欠損データは削除することも多いでしょう。以下はそのような場合のコードです。

欠損値のある行を削除(※how='any'は欠損がひとつでもあれば、その行を削除)
df = df.dropna(how='any')

#別の方法
df.dropna(subset=[‘列名‘], axis=0, inplace=True) #axis=0(デフォルト)で行、inplace=Trueでdf変更反映
[‘-‘]を本来の欠損値(nan)に変換
df = df[‘列名‘].replace(‘-‘,np.nan)
すべての欠損値を0で置換
df.fillna(0)
すべての値が欠損している列を削除
nonnull_list = []
for col in df.columns:
    nonnull = df[col].count()
    if nonnull = 0:
        nonnull_list.append(col)
df.drop(nonnull_list, axis=1)
ある列を削除する
df.drop('列名', axis=1)
欠損値を平均値や最頻値等で穴埋め(列毎の値を算出して穴埋め)
df.fillna(df.mean()) # 平均値で補完
特定列の欠損値を中央値で穴埋め
df['Age'].fillna(df['Age'].median(), inplace=True)  # 中央値で補完
特定列の欠損値をある値•文字で穴埋め
df['Embarked'].fillna('S', inplace=True)         # 'S'で補完

7. ラベルエンコーディング(カテゴリーデータの数値変換)

SexとEmbarkedはカテゴリーデータなので、ラベルエンコーディングを行いました。まずはSexです。以下のコードの実行でmale=0に、female=1となりました。

LabelEncoder
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
le.fit(df['Sex'])
df['Sex'] = le.transform(df['Sex'])

df.head()

image.png

つぎはEmbarkedです。以下のコードの実行でEmbarkedも0,1,2にエンコーディングできました。

LabelEncoder
from sklearn.preprocessing import LabelEncoder

le = LabelEncoder()
le.fit(df['Embarked'])
df['Embarked'] = le.transform(df['Embarked'])

df.head()

image.png

その他のエンコーディング

データに混在する文字列(文字データA)を任意の値(10)に変換
df['列の名前'] = df['列の名前'].replace('文字データA', 10). astype(float)
df['列の名前'].value_counts()
データに混在する文字データ⁽A,B,C)を任意の値(45,75,120)に変換
dic = {
    '文字データA': 45
    '文字データB': 75
    '文字データC': 120}
df['列の名前'] = df['列の名前'].replace(dic). astype(float)
df['列の名前'].value_counts()
[平成20年]から数値(20)だけを取り出す
df["和暦"].value_counts().keys() [1].split("平成")[1].split("年")[0)
#20
和暦から年を取り出して西暦に換算
y_list = {}
for 1 in df["和暦"].value_counts().keys():
    if "平成" in i:
        num = float(i.split("平成")[1].split("年")[0])
        year = 33 - num
for 1 in df["和暦"].value_counts().keys():
    if "令和" in i:
        num = float(i.split("令和")[1].split("年")[0])
        year = 3 - num
for 1 in df["和暦"].value_counts().keys():
    if "昭和" in i:
        num = float(i.split("昭和")[1].split("年")[0])
        year = 96 - num
y_list(i) = year
df("和暦") = df("和暦").replace(y_list)
2020年第1四半期→2020.25に変換
"2020年第1四半期".replace("年第1四半期",  ".25 ") #第一引数に置換元文字列、第二引数に置換先文字列を指定
2020年第n四半期→2020.25,2020.50,2020.75,2020.99に
year = {
    "年第1四半期": ".25 ",
    "年第2四半期": ".50",
    "年第3四半期": ".75",
    "年第4四半期": ".99"
}

#year = {k,j} 
year_list = {}
for i in df[”取引時点"].value_counts().keys():
    for k, j in year. items():
        if k in i:
            year_rep = i.replace(k, j)
    year_list [i] = year_rep
df[”取引時点"] = df[”取引時点"].replace(year_list).astype(float)
df.info()
カウントエンコーディング
import collections

counter = collections.Counter(df['Ticket'].values)
count_dict = dict(counter.most_common())
encoded = df['Ticket'].map(lambda x: count_dict[x]).values
df['TicketID'] = encoded

print('エンコード結果: ', encoded)
dtypeを変更
df['Age']=df['Age'].astype(int)
df.head()
# 'size'を'XL','L','M' → 整数(1, 2, 3)に変換
size_mapping = {'XL': 3, 'L': 2, 'M': 1}  
df['size'] = df['size'].map(size_mapping)
df.head()
category_encoders
pip install category_encoders

#Ordinal_Encode
import category_encoders as ce

# Eoncodeしたい列をリストで指定。もちろん複数指定可能。
list_cols = ['列A','列B','列C','列D']

# 序数をカテゴリに付与して変換
ce_oe = ce.OrdinalEncoder(cols=list_cols,handle_unknown='impute')
df_ce_ordinal = ce_oe.fit_transform(df)

df_ce_ordinal.head()

8. 新たな特徴量の生成

SibSpは、兄弟,配偶者の数。 Parchは、両親,子供の数 です。この2つの説明変数はいわば家族の数です。またこの2つ相関も高く、このまま分析に使うのはよくありませんので、足し合わせて新しい変数にしました。(これは「Kaggleスタートブック」に紹介されていた内容です)

df['FamilySize'] = df['Parch'] + df['SibSp'] + 1
sns.countplot(y='FamilySize', data = df, hue='Survived')

df.head()

image.png

これも「Kaggleスタートブック」に紹介されていた内容ですが、FamilySizeが1という方が大部分です。またグラフの通り、FamilySizeが1の方は生存率が低い傾向もみられ案す。このことからFamilySizeが1であるか否かを特徴として取り上げてもよさそうです、

df['IsAlone'] = 0
df.loc[df['FamilySize'] == 1, 'IsAlone'] = 1
df.head()

image.png

最後に、FamilySizeに統合したSibSpとParchのデータは列ごと削除しました。

SipSp列の削除
df.drop("SibSp", axis = 1, inplace = True)
df.head()

image.png

Parch列の削除
df.drop("Parch", axis = 1, inplace = True)
df.head()

image.png

df.info()

image.png

欠損値合計の確認
df.isnull().sum()

image.png

これで欠損値もなくなり、前処理は無事に終了しました。
ここでは「PassengerId」は残していますが、これも削除して問題ないと思います。

参考:Nameから敬称を抽出し特徴量に

#Nameの確認
df = pd.read_csv('train.csv')
df['Name'].head()

0 Braund, Mr. Owen Harris
1 Cumings, Mrs. John Bradley (Florence Briggs Th...
2 Heikkinen, Miss. Laina
3 Futrelle, Mrs. Jacques Heath (Lily May Peel)
4 Allen, Mr. William Henry
Name: Name, dtype: object

#Nameを[family‗name,honorific.name]に区分(引数2はなくても実行されたがNone列含む4分割となった)
name_df = df["Name"].str.split("[,.]",2, expand=True)

#columnを設定
name_df.columns = ["family_name", "honorific", "name"]
name_df.head()

image.png

#各列にstrip()を用いることで先頭と末尾の空白文字が削除される(念のため各列に適用)
name_df["family_name"] = name_df["family_name"].str.strip()
name_df["honorific"] = name_df["honorific"].str.strip()
name_df["name"] = name_df["name"].str.strip()
name_df.head()
#dfにname_dfを列結合
df = pd.concat([df, name_df], axis=1)
#グラフで確認する
sns.boxplot(x="honorific", y="Age", data=df)

image.png

#Mr、Miss、Mrs、Master以外の敬称はotherとして統合
df.loc[~((df ["honorific"] =="Mr")|(df [ "honorific"] =="Miss")|(df[ "honorific"] =="Mrs")|(df [ "honorific"]  =="Master")),
"honorific"] = "other"
df.head()
pip install scikit-learn
from sklearn.preprocessing import LabelEncoder

#honorificをラベルエンコード
le = LabelEncoder()
le = le.fit(df ["honorific"])
df["honorific"] = le.transform(df["honorific"])
df.head()
ラベルエンコード後のグラフ化
sns.boxplot(x="honorific", y="Age", data=df)

image.png

私は実行していないが、年齢の欠損値を、敬称ごとの平均年齢で補完されている例があった。
敬称ごとの平均年齢を求め、年齢が欠損しているデータに敬称の平均年齢を補完。
※補完後、敬称ごとの平均年齢を求めたhonorific_Age は不要のため削除。

#敬称ごとの平均年齢で年齢が欠損しているデータを穴埋め
honorific_age_mean = df [["honorific", "Age"]].groupby("honorific").mean().reset_index()
honorific_age_mean.columns = ["honorific", "honorific_Age" ]

df = pd. merge (df, honorific_age_nean,on="honorific", how="left")
df.loc [(df ["Age"].isnull()), "Age"] = df["honorific_Age"]
df = df.drop([ "honorific_Age"), axis=1)

その他の便利なコード記述

説明変数と目的変数の設定を一発で

df右端のcolumnを目的変数、他のcolumnsを説明変数に設定
FEATURES = df.columns[:-1]
TARGET = df.columns[-1]
上記設定の上でX,yを以下の設定すれば個別設定の必要なし
X = df.loc[:, FEATURES]
y = df.loc[:, TARGET]
[列A]を先頭列に移動
first_column = df.pop('列A')

df.insert(0,'列A',first_column)

df

最後に

前処理はとても億劫であったが、やってみたら・・・まぁやれなくないなという感じです。 
これで完璧!といえるかどうかは別として、このあたりまで処理した後、Pycaret活用・・・といった流れの方が現実的なのかもしれません。
 

参考サイト

参考書籍

4
6
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
6