ウェブサイト検索

Python でデータ クリーニングの技術を習得する


Python でデータをクリーンアップし、データ サイエンス プロジェクトで使用できるようにする方法。

データ クリーニングは、あらゆるデータ分析プロセスの重要な部分です。これは、エラーを削除し、欠落しているデータを処理し、データが作業可能な形式であることを確認するステップです。データセットが適切にクリーニングされていないと、その後の分析が歪んだり、不正確になったりする可能性があります。

この記事では、pandas、numpy、seaborn、matplotlib などの強力なライブラリを使用して、Python でデータ クリーニングを行うためのいくつかの主要なテクニックを紹介します。

データクリーニングの重要性を理解する

データクリーニングの仕組みに入る前に、その重要性を理解しましょう。現実世界のデータは乱雑であることがよくあります。重複したエントリ、不正または一貫性のないデータ型、欠損値、無関係な特徴、外れ値が含まれる可能性があります。これらすべての要因は、データ分析時に誤解を招く結論につながる可能性があります。このため、データ クリーニングはデータ サイエンスのライフサイクルにおいて不可欠な部分となっています。

次のデータ クリーニング タスクについて説明します。

Python でのデータ クリーニングのセットアップ

始める前に、必要なライブラリをインポートしましょう。データ操作には pandas を使用し、視覚化には seaborn と matplotlib を使用します。

日付を操作するための datetime Python モジュールもインポートします。

import pandas as pd
import seaborn as sns
import datetime as dt
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker

データのロードと検査

まず、データをロードする必要があります。この例では、pandas を使用して CSV ファイルをロードします。区切り文字引数も追加します。

df = pd.read_csv('F:\\KDNuggets\\KDN Mastering the Art of Data Cleaning in Python\\property.csv', delimiter= ';')

次に、データを検査して、その構造、どのような種類の変数を扱っているか、欠損値があるかどうかを理解することが重要です。インポートしたデータはそれほど大きくないので、データセット全体を見てみましょう。

# Look at all the rows of the dataframe
display(df)

データセットは次のようになります。

いくつかの欠損値があることがすぐにわかります。また、日付の形式も一貫していません。

次に、info() メソッドを使用して DataFrame の概要を見てみましょう。

# Get a concise summary of the dataframe
print(df.info())

コード出力は次のとおりです。

列 square_feet のみに NULL 値がないことがわかります。そのため、これを何らかの方法で処理する必要があります。また、列advertising_dateおよびsale_dateは、日付である必要がありますが、オブジェクトのデータ型です。

列の位置が完全に空です。それは必要ですか?

これらの問題に対処する方法を紹介します。まずは不要な列を削除する方法を学びましょう。

不要な列を削除する

データセットにはデータ分析に不要な列が 2 つあるので、それらを削除します。

最初の列は購入者です。購入者の名前は分析に影響を与えないため、必要ありません。

指定された列名でdrop()メソッドを使用しています。列を削除することを指定するために、軸を 1 に設定します。また、既存の DataFrame を変更し、削除された列なしで新しい DataFrame を作成しないように、inplace 引数は True に設定されています。

df.drop('buyer', axis = 1, inplace = True)

削除する 2 番目の列は場所です。この情報があると便利かもしれませんが、これは完全に空の列なので、そのまま削除しましょう。

最初の列と同じアプローチを採用します。

df.drop('location', axis = 1, inplace = True)

もちろん、これら 2 つの列を同時に削除することもできます。

df = df.drop(['buyer', 'location'], axis=1)

どちらのアプローチでも次のデータフレームが返されます。

重複データの処理

さまざまな理由でデータセット内に重複データが発生する可能性があり、分析が歪む可能性があります。

データセット内の重複を検出してみましょう。その方法は次のとおりです。

以下のコードは、メソッド duplicated() を使用して、データセット全体の重複を考慮します。デフォルト設定では、値の最初の出現を一意とみなし、その後の出現を重複とみなされます。この動作はkeep パラメータを使用して変更できます。たとえば、 df.duplicated(keep=False) は、最初に出現したものを含むすべての重複を True としてマークします。

# Detecting duplicates
duplicates = df[df.duplicated()]
duplicates

これが出力です。

同じ値を持つ行 2 が最初に出現するため、インデックス 3 を持つ行は重複としてマークされています。

次に、重複を削除する必要があります。これを次のコードで行います。

# Detecting duplicates
duplicates = df[df.duplicated()]
duplicates

drop_duplicates() 関数は、重複を識別する際にすべての列を考慮します。特定の列のみを考慮したい場合は、df.drop_duplicates(subset=['column1', 'column2']) のように、リストとしてこの関数に渡すことができます。

ご覧のとおり、重複した行が削除されています。ただし、インデックス付けは同じままで、インデックス 3 が欠落しています。インデックスをリセットしてこれを整理します。

df = df.reset_index(drop=True)

このタスクはreset_index() 関数を使用して実行されます。  drop=True 引数は、元のインデックスを破棄するために使用されます。この引数を含めない場合、古いインデックスが DataFrame の新しい列として追加されます。 Drop=True を設定すると、古いインデックスを忘れてデフォルトの整数インデックスにリセットするようにパンダに指示します。

練習のために、この Microsoft データセットから重複を削除してみてください。

データ型変換

場合によっては、データ型が正しく設定されていない可能性があります。たとえば、日付列は文字列として解釈される可能性があります。これらを適切な型に変換する必要があります。

私たちのデータセットでは、オブジェクト データ型として表示されている列 Advertise_date と sale_date に対してこれを行います。また、日付の形式は行ごとに異なります。それを日付に変換するとともに、一貫性を持たせる必要があります。

最も簡単な方法は、to_datetime() メソッドを使用することです。ここでも、以下に示すように、列ごとに実行できます。

その際、一部の日付が日から始まるため、dayfirst 引数を True に設定します。

# Converting advertisement_date column to datetime
df['advertisement_date'] = pd.to_datetime(df['advertisement_date'], dayfirst = True)

# Converting sale_date column to datetime
df['sale_date'] = pd.to_datetime(df['sale_date'], dayfirst = True)

apply() メソッドと to_datetime() を使用して、両方の列を同時に変換することもできます。

# Converting advertisement_date and sale_date columns to datetime
df[['advertisement_date', 'sale_date']] = df[['advertisement_date', 'sale_date']].apply(pd.to_datetime, dayfirst =  True)

どちらのアプローチでも同じ結果が得られます。

これで、日付が一貫した形式になりました。すべてのデータが変換されていないことがわかります。 Advertise_date には 1 つの NaT 値があり、sale_date には 2 つの NaT 値があります。これは日付が欠落していることを意味します。

info() メソッドを使用して、列が日付に変換されているかどうかを確認してみましょう。

# Get a concise summary of the dataframe
print(df.info())

ご覧のとおり、両方の列は datetime64[ns] 形式ではありません。

次に、この Airbnb データセットでデータを TEXT から NUMERIC に変換してみます。

欠損データの処理

現実世界のデータセットには欠損値が存在することがよくあります。特定のアルゴリズムではそのような値を処理できないため、欠損データの処理は非常に重要です。

この例には欠損値もいくつか含まれているため、欠損データを処理するための最も一般的な 2 つのアプローチを見てみましょう。

欠損値のある行の削除

欠損データのある行の数が観測値の総数に比べて重要でない場合は、これらの行を削除することを検討してください。

この例では、最後の行には平方フィートと広告日以外の値がありません。このようなデータは使用できないので、この行を削除しましょう。

行のインデックスを示すコードは次のとおりです。

df = df.drop(8)

DataFrame は次のようになります。

最後の行が削除され、DataFrame の見た目が良くなりました。ただし、まだ不足しているデータがいくつかありますので、別のアプローチを使用して処理します。

欠損値の代入

重大な欠損データがある場合は、削除よりも補完の方が良い戦略になる可能性があります。このプロセスには、他のデータに基づいて欠損値を埋めることが含まれます。数値データの場合、一般的な補完方法には、中心傾向の尺度 (平均、中央値、最頻値) の使用が含まれます。

すでに変更されたデータフレームでは、advertising_date 列と sale_date 列に NaT (Not a Time) 値があります。これらの欠損値をmean() メソッドを使用して代入します。

このコードでは、fillna() メソッドを使用して、Null 値を検索し、平均値を入力します。

# Imputing values for numerical columns
df['advertisement_date'] = df['advertisement_date'].fillna(df['advertisement_date'].mean())
df['sale_date'] = df['sale_date'].fillna(df['sale_date'].mean())

1 行のコードで同じことを行うこともできます。 apply() を使用して、ラムダ を使用して定義された関数を適用します。上記と同様に、この関数は fillna() メソッドと mean() メソッドを使用して欠損値を埋めます。

# Imputing values for multiple numerical columns
df[['advertisement_date', 'sale_date']] = df[['advertisement_date', 'sale_date']].apply(lambda x: x.fillna(x.mean()))

どちらの場合も出力は次のようになります。

sale_date 列には不要な時間が含まれています。それらを削除しましょう。

strftime() メソッドを使用して、日付を文字列表現と特定の形式に変換します。

df['sale_date'] = df['sale_date'].dt.strftime('%Y-%m-%d')

日付がすべて整然と表示されるようになりました。

複数の列で strftime() を使用する必要がある場合は、次の方法で再び lambda を使用できます。

df[['date1_formatted', 'date2_formatted']] = df[['date1', 'date2']].apply(lambda x: x.dt.strftime('%Y-%m-%d'))

ここで、欠落しているカテゴリ値を代入する方法を見てみましょう。

カテゴリ データは、類似した特性を持つ情報をグループ化するために使用されるデータのタイプです。これらの各グループはカテゴリです。カテゴリ データは数値 (「男性」を示す「1」、「女性」を示す「2」など) を取ることができますが、それらの数値は数学的な意味を持ちません。たとえば、それらを一緒に追加することはできません。

カテゴリデータは通常、次の 2 つのカテゴリに分類されます。

  1. 名目データ: これは、カテゴリにラベルが付けられているだけで、特定の順序で並べることができない場合です。例としては、性別 (男性、女性)、血液型 (A、B、AB、O)、色 (赤、緑、青) などが挙げられます。

  1. 順序データ: カテゴリを順序付けまたはランク付けできる場合です。カテゴリ間の間隔は等間隔ではありませんが、カテゴリの順序には意味があります。例には、評価スケール (映画の 1 ~ 5 の評価)、教育レベル (高校、学部、大学院)、またはがんの段階 (ステージ I、ステージ II、ステージ III) が含まれます。

欠落しているカテゴリデータを代入するには、通常、このモードが使用されます。この例では、列 property_category はカテゴリ (公称) データですが、2 つの行でデータが欠落しています。

欠損値をモードに置き換えましょう。

# For categorical columns
df['property_category'] = df['property_category'].fillna(df['property_category'].mode()[0])

このコードでは、fillna() 関数を使用して、property_category 列内のすべての NaN 値を置き換えます。それをモードに置き換えます。

さらに、[0] 部分は、このシリーズから最初の値を抽出するために使用されます。複数のモードがある場合は、最初のモードが選択されます。モードが 1 つしかない場合でも、正常に動作します。

これが出力です。

データはかなり良くなりました。残っている唯一のことは、外れ値があるかどうかを確認することです。

このメタ インタビューの質問で NULL を扱う練習をすることができます。NULL をゼロに置き換える必要があります。

外れ値への対処

外れ値は、他の観測値とは明らかに異なる、データセット内のデータ ポイントです。それらは、データセット内の他の値から非常に遠く離れて、全体的なパターンの外側に存在する場合があります。これらの値は、残りのデータと比較して著しく高いか低いため、異常であると考えられます。

異常値は、次のようなさまざまな理由で発生する可能性があります。

  • 測定または入力エラー

  • データの破損
  • 真の統計的異常

外れ値は、データ分析と統計モデリングの結果に大きな影響を与える可能性があります。これらは、偏った分布や偏りを引き起こしたり、基礎となる統計的仮定を無効にしたり、推定されたモデルの適合を歪めたり、予測モデルの予測精度を低下させたり、誤った結論を導き出す可能性があります。

外れ値を検出するために一般的に使用される方法には、Z スコア、IQR (四分位範囲)、箱ひげ図、散布図、データ視覚化手法などがあります。一部の高度なケースでは、機械学習手法も使用されます。

データを視覚化すると、外れ値を特定するのに役立ちます。 Seaborn の箱ひげ図はこれに便利です。

plt.figure(figsize=(10, 6))
sns.boxplot(data=df[['advertised_price', 'sale_price']])

plt.figure() を使用して、Figure の幅と高さをインチ単位で設定します。

次に、advertised_price 列と sale_price 列の箱ひげ図を作成します。これは次のようになります。

これを上記のコードに追加することで、プロットを改良して使いやすくすることができます。

plt.xlabel('Prices')
plt.ylabel('USD')
plt.ticklabel_format(style='plain', axis='y')
formatter = ticker.FuncFormatter(lambda x, p: format(x, ',.2f'))
plt.gca().yaxis.set_major_formatter(formatter)

上記のコードを使用して、両方の軸のラベルを設定します。また、Y 軸の値は科学表記法であり、それを価格値に使用できないことにも気付きます。そこで、 plt.ticklabel_format() 関数を使用して、これをプレーン スタイルに変更します。

次に、千の位の区切り文字としてカンマと小数点を使用して y 軸の値を表示するフォーマッタを作成します。最後のコード行はこれを軸に適用します。

出力は次のようになります。

では、外れ値を特定して削除するにはどうすればよいでしょうか?

その方法の 1 つは IQR メソッドを使用することです。

IQR (四分位範囲) は、データセットを四分位に分割することによって変動を測定するために使用される統計手法です。四分位は、ランク順のデータ セットを 4 つの等しい部分に分割し、第 1 四分位 (25 パーセンタイル) と第 3 四分位 (75 パーセンタイル) の範囲内の値が四分位範囲を構成します。

四分位範囲は、データ内の外れ値を特定するために使用されます。仕組みは次のとおりです。

  1. まず、第 1 四分位 (Q1)、第 3 四分位 (Q3) を計算し、IQR を決定します。 IQR は Q3 - Q1 として計算されます。

  2. Q1 - 1.5IQR を下回る値、または Q3 + 1.5IQR を超える値は外れ値とみなされます。

箱ひげ図では、箱は実際に IQR を表します。ボックス内の線は中央値 (または第 2 四分位数) です。箱ひげ図の「ひげ」は、Q1 と Q3 から 1.5*IQR 以内の範囲を表します。

これらのひげの外側にあるデータ ポイントは外れ値と見なすことができます。私たちの場合、それは 1,200 万ドルの価値です。箱ひげ図を見ると、これがいかに明確に表現されているかがわかります。これは、外れ値を検出する際にデータの視覚化がなぜ重要であるかを示しています。

次に、Python コードで IQR メソッドを使用して外れ値を削除しましょう。まず、宣伝されている価格の外れ値を削除します。

Q1 = df['advertised_price'].quantile(0.25)
Q3 = df['advertised_price'].quantile(0.75)
IQR = Q3 - Q1
df = df[~((df['advertised_price'] < (Q1 - 1.5 * IQR)) |(df['advertised_price'] > (Q3 + 1.5 * IQR)))]

まず、quantile() 関数を使用して、最初の四分位数 (または 25 番目の百分位数) を計算します。第 3 四分位数または 75 パーセンタイルについても同じことを行います。

これらは、それぞれデータの 25% と 75% が下回る値を示しています。

次に、四分位間の差を計算します。ここまでの作業はすべて、IQR のステップを Python コードに変換しているだけです。

最後のステップとして、外れ値を削除します。つまり、Q1 - 1.5 * IQR 未満、または Q3 + 1.5 * IQR を超えるすべてのデータです。

「~」演算子は条件を否定するため、外れ値ではないデータのみが残ります。

次に、販売価格でも同じことができます。

Q1 = df['sale_price'].quantile(0.25)
Q3 = df['sale_price'].quantile(0.75)
IQR = Q3 - Q1
df = df[~((df['sale_price'] < (Q1 - 1.5 * IQR)) |(df['sale_price'] > (Q3 + 1.5 * IQR)))]

もちろん、for ループを使用すると、より簡潔な方法でこれを行うことができます。

for column in ['advertised_price', 'sale_price']:
    Q1 = df[column].quantile(0.25)
    Q3 = df[column].quantile(0.75)
    IQR = Q3 - Q1
    df = df[~((df[column] < (Q1 - 1.5 * IQR)) |(df[column] > (Q3 + 1.5 * IQR)))]

ループは 2 つの列を繰り返します。列ごとに IQR を計算し、DataFrame 内の行を削除します。

この操作は、最初にadvertised_priceに対して、次にsale_priceに対してという順番で実行されることに注意してください。その結果、DataFrame は各列のインプレースで変更され、いずれかの列で外れ値であるために行が削除される可能性があります。したがって、この操作では、advertised_price と sale_price の外れ値を個別に削除し、後で結果を結合する場合よりも行数が少なくなる可能性があります。

この例では、どちらの場合でも出力は同じになります。箱ひげ図がどのように変化したかを確認するには、前と同じコードを使用して箱ひげ図を再度プロットする必要があります。

plt.figure(figsize=(10, 6))
sns.boxplot(data=df[['advertised_price', 'sale_price']])
plt.xlabel('Prices')
plt.ylabel('USD')
plt.ticklabel_format(style='plain', axis='y')
formatter = ticker.FuncFormatter(lambda x, p: format(x, ',.2f'))
plt.gca().yaxis.set_major_formatter(formatter)

これが出力です。

総会のインタビューの質問を解くことで、Python でパーセンタイルの計算を練習できます。

結論

データ クリーニングは、データ分析プロセスにおける重要なステップです。時間がかかる場合がありますが、結果の正確性を確保することが重要です。

幸いなことに、Python のライブラリの豊富なエコシステムにより、このプロセスはより管理しやすくなります。不要な行と列を削除し、データを再フォーマットし、欠損値や外れ値に対処する方法を学びました。これらは、ほとんどのデータに対して実行する必要がある通常の手順です。ただし、2 つの列を 1 つに結合したり、既存のデータを確認したり、ラベルを割り当てたり、空白を削除したりする必要がある場合もあります。

これはすべてデータ クリーニングです。これにより、乱雑な現実世界のデータを、自信を持って分析できる適切に構造化されたデータセットに変えることができます。最初に使用したデータセットと最終的に完成したデータセットを比較してください。

この結果に満足できず、きれいなデータに妙に興奮しないとしたら、一体データ サイエンスで何をしているのでしょう?
 

関連記事