Python
 Computer >> コンピューター >  >> プログラミング >> Python

Pythonで顧客離反(チャーン)を予測する方法:機械学習による実践ガイド

あらゆるビジネスは顧客のロイヤルティに依存しています。リピート購入は企業の収益性を支える重要な柱の一つであり、顧客が離れていく理由を把握することは極めて重要です。このように顧客が離反していく現象は「カスタマーチャーン(Customer Churn)」と呼ばれます。過去の傾向を分析することで、どのような要因が顧客離反に影響を与えているのかを把握し、特定の顧客が離反するかどうかを予測できるようになります。

本記事では、機械学習アルゴリズムを使って過去の顧客離反データの傾向を分析し、どの顧客が離反する可能性が高いかを判定する方法を解説します。

データの準備

例として、通信業界(Telecom)の顧客離反データを使用します。データセットはKaggleで公開されており、ダウンロードURLは後述のプログラム内にコメントとして記載しています。Pandasライブラリを使ってCSVファイルをPythonプログラムに読み込み、サンプル行を確認してみましょう。

コード例

import pandas as pd
#Loading the Telco-Customer-Churn.csv dataset
#https://www.kaggle.com/blastchar/telco-customer-churn
datainput = pd.read_csv('E:\\Telecom_customers.csv')
print("Given input data :\n",datainput)

出力結果

上記のコードを実行すると、次のような結果が得られます。

Given input data :
        customerID        gender   SeniorCitizen  ...    MonthlyCharges    TotalCharges       Churn
0       7590-VHVEG        Female               0  ...             29.85            29.85          No
1       5575-GNVDE          Male               0  ...             56.95           1889.5          No
2       3668-QPYBK          Male               0  ...             53.85           108.15         Yes
3       7795-CFOCW          Male               0  ...             42.30          1840.75          No
4       9237-HQITU        Female               0  ...             70.70           151.65         Yes
...            ...          ...             ...  ...               ...              ...         ...
7038    6840-RESVB          Male               0  ...             84.80           1990.5          No
7039    2234-XADUH        Female               0  ...            103.20           7362.9          No
7040    4801-JZAZL        Female               0  ...             29.60           346.45          No
7041    8361-LTMKD          Male               1  ...             74.40            306.6         Yes
7042    3186-AJIEK          Male               0  ...            105.65           6844.5          No

[7043 rows x 21 columns]

このデータセットには7,043件の顧客情報と21個の列(性別、契約内容、月額料金、解約有無など)が含まれています。

既存パターンの分析

次に、データセットを分析して離反が発生する際の既存パターンを探します。また、予測に影響しない不要な列は削除します。たとえば「customerID(顧客ID)」の列は、顧客が離反するかどうかに影響を与えないため、dropメソッドやpopメソッドを使って削除します。その後、データセット内における離反率を円グラフで可視化します。

コード例

import pandas as pd
import matplotlib.pyplot as plt
from matplotlib import rcParams

#Loading the Telco-Customer-Churn.csv dataset
#https://www.kaggle.com/blastchar/telco-customer-churn
datainput = pd.read_csv('E:\\Telecom_customers.csv')
print("Given input data :\n",datainput)

#Dropping columns
datainput.drop(['customerID'], axis=1, inplace=True)
datainput.pop('TotalCharges')
datainput['OnlineBackup'].unique()

data = datainput['Churn'].value_counts(sort = True)
chroma = ["#BDFCC9","#FFDEAD"]
rcParams['figure.figsize'] = 9,9
explode = [0.2,0.2]
plt.pie(data, explode=explode, colors=chroma, autopct='%1.1f%%', shadow=True, startangle=180,)
plt.title('Percentage of Churn in the given Data')
plt.show()

出力結果

上記のコードを実行すると、次のような円グラフが表示されます。

Pythonで顧客離反(チャーン)を予測する方法:機械学習による実践ガイド

グラフから、データセット全体のうち約26.5%の顧客が離反していることが確認できます。これは通信業界において無視できない割合であり、離反要因の分析と予測モデルの構築が重要であることを示しています。

データの前処理

機械学習アルゴリズムで扱えるようにするため、すべてのフィールドにラベルを付与し、テキスト値を数値フラグへ変換します。たとえば、「gender(性別)」列の値は「Male / Female」という文字列ではなく、0と1という数値に置き換えられます。これにより、これらのフィールドを計算やアルゴリズムで利用でき、各項目が離反値に与える影響を評価できるようになります。ここではscikit-learnのLabelEncoderメソッドを使用します。

コード例

import pandas as pd
from sklearn import preprocessing

label_encoder = preprocessing.LabelEncoder()
datainput['gender'] = label_encoder.fit_transform(datainput['gender'])
datainput['Partner'] = label_encoder.fit_transform(datainput['Partner'])
datainput['Dependents'] = label_encoder.fit_transform(datainput['Dependents'])
datainput['PhoneService'] = label_encoder.fit_transform(datainput['PhoneService'])
datainput['MultipleLines'] = label_encoder.fit_transform(datainput['MultipleLines'])
datainput['InternetService'] = label_encoder.fit_transform(datainput['InternetService'])
datainput['OnlineSecurity'] = label_encoder.fit_transform(datainput['OnlineSecurity'])
datainput['OnlineBackup'] = label_encoder.fit_transform(datainput['OnlineBackup'])
datainput['DeviceProtection'] = label_encoder.fit_transform(datainput['DeviceProtection'])
datainput['TechSupport'] = label_encoder.fit_transform(datainput['TechSupport'])
datainput['StreamingTV'] = label_encoder.fit_transform(datainput['StreamingTV'])
datainput['StreamingMovies'] = label_encoder.fit_transform(datainput['StreamingMovies'])
datainput['Contract'] = label_encoder.fit_transform(datainput['Contract'])
datainput['PaperlessBilling'] = label_encoder.fit_transform(datainput['PaperlessBilling'])
datainput['PaymentMethod'] = label_encoder.fit_transform(datainput['PaymentMethod'])
datainput['Churn'] = label_encoder.fit_transform(datainput['Churn'])
print("input data after label encoder :\n",datainput)

#separating features(X) and label(y)
datainput["Churn"] = datainput["Churn"].astype(int)
y = datainput["Churn"].values
X = datainput.drop(labels = ["Churn"],axis = 1)
print("\nseparated X and y :")
print("y -",y)
print("X -",X)

出力結果

上記のコードを実行すると、次のような結果が得られます。

input data after label encoder
      customerID      gender   SeniorCitizen ... MonthlyCharges TotalCharges     Churn
0     7590-VHVEG           0                0 ... 29.85          29.85             0
1     5575-GNVDE           1                0 ... 56.95          1889.5            0
2     3668-QPYBK           1                0 ... 53.85          108.15            1
3     7795-CFOCW           1                0 ... 42.30          1840.75           0
4     9237-HQITU           0                0 ... 70.70          151.65            1
... ... ... ... ... ... ... ...
7038      6840-RESVB      1                0 ...     84.80      1990.5            0
7039      2234-XADUH      0                0 ...    103.20      7362.9            0
7040      4801-JZAZL      0                0 ...     29.60      346.45            0
7041      8361-LTMKD      1                1 ...     74.40       306.6            1
7042      3186-AJIEK      1                0 ...    105.65      6844.5            0

[7043 rows x 21 columns]

separated X and y :
y - [0 0 1 ... 0 1 0]
X -       customerID      gender ...       MonthlyCharges      TotalCharges
0           7590-VHVEG           0 ...             29.85                  29.85
1           5575-GNVDE           1 ...             56.95                 1889.5
2           3668-QPYBK           1 ...             53.85                  108.15
3           7795-CFOCW           1 ...             42.30                 1840.75
4           9237-HQITU           0 ...             70.70                  151.65
...                ...         ... ...                 ...                    ...
7038        6840-RESVB           1 ...             84.80                  1990.5
7039        2234-XADUH           0 ...            103.20                  7362.9
7040        4801-JZAZL           0 ...             29.60                  346.45
7041        8361-LTMKD           1 ...             74.40                   306.6
7042        3186-AJIEK           1 ...            105.65                  6844.5

[7043 rows x 20 columns]

ここでは、目的変数(予測したい値)である「Churn」を y として分離し、それ以外の説明変数を X として切り出しています。

データの学習とテスト

次に、データセットを学習用とテスト用の2つに分割します。test_sizeパラメータは、テスト専用に使うデータの割合を決定するものです。ここでは20%をテストデータとしています。この分割によって、構築したモデルの信頼性を検証できます。その後、ロジスティック回帰(Logistic Regression)アルゴリズムを適用し、予測値を算出します。

コード例

import pandas as pd
import warnings
warnings.filterwarnings("ignore")

from sklearn.linear_model import LogisticRegression

#Loading the Telco-Customer-Churn.csv dataset with pandas
datainput = pd.read_csv('E:\\Telecom_customers.csv')

datainput.drop(['customerID'], axis=1, inplace=True)
datainput.pop('TotalCharges')
datainput['OnlineBackup'].unique()

#LabelEncoder()
from sklearn import preprocessing
label_encoder = preprocessing.LabelEncoder()
datainput['gender'] = label_encoder.fit_transform(datainput['gender'])
datainput['Partner'] = label_encoder.fit_transform(datainput['Partner'])
datainput['Dependents'] = label_encoder.fit_transform(datainput['Dependents'])
datainput['PhoneService'] = label_encoder.fit_transform(datainput['PhoneService'])
datainput['MultipleLines'] = label_encoder.fit_transform(datainput['MultipleLines'])
datainput['InternetService'] = label_encoder.fit_transform(datainput['InternetService'])
datainput['OnlineSecurity'] = label_encoder.fit_transform(datainput['OnlineSecurity'])
datainput['OnlineBackup'] = label_encoder.fit_transform(datainput['OnlineBackup'])
datainput['DeviceProtection'] = label_encoder.fit_transform(datainput['DeviceProtection'])
datainput['TechSupport'] = label_encoder.fit_transform(datainput['TechSupport'])
datainput['StreamingTV'] = label_encoder.fit_transform(datainput['StreamingTV'])
datainput['StreamingMovies'] = label_encoder.fit_transform(datainput['StreamingMovies'])
datainput['Contract'] = label_encoder.fit_transform(datainput['Contract'])
datainput['PaperlessBilling'] = label_encoder.fit_transform(datainput['PaperlessBilling'])
datainput['PaymentMethod'] = label_encoder.fit_transform(datainput['PaymentMethod'])
datainput['Churn'] = label_encoder.fit_transform(datainput['Churn'])
#print("input data after label encoder :\n",datainput)

#separating features(X) and label(y)
datainput["Churn"] = datainput["Churn"].astype(int)
Y = datainput["Churn"].values
X = datainput.drop(labels = ["Churn"],axis = 1)

#train_test_split method
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

#LogisticRegression
classifier=LogisticRegression()
classifier.fit(X_train,Y_train)
Y_pred=classifier.predict(X_test)
print("\npredicted values :\n",Y_pred)

出力結果

上記のコードを実行すると、次のような結果が得られます。

predicted values :
[0 0 1 ... 0 1 0]

評価指標の算出

上記ステップでの精度が許容範囲に達したら、さまざまなパラメータを求めてモデルをさらに評価していきます。ここでは「正解率(Accuracy)」と「混同行列(Confusion Matrix)」を評価指標として使用し、モデルがどの程度正確に動作しているかを判断します。正解率が高いほど、モデルの適合度が高いことを示します。また、混同行列は真陽性・真陰性・偽陽性・偽陰性を行列形式で表したものであり、偽の値に対して真の値の割合が高いほど、優れたモデルであることを意味します。

コード例

import pandas as pd
import warnings
warnings.filterwarnings("ignore")
from sklearn.linear_model import LogisticRegression
from sklearn import metrics
from sklearn.metrics import confusion_matrix

#Loading the Telco-Customer-Churn.csv dataset with pandas
datainput = pd.read_csv('E:\\Telecom_customers.csv')

datainput.drop(['customerID'], axis=1, inplace=True)
datainput.pop('TotalCharges')
datainput['OnlineBackup'].unique()

#LabelEncoder()
from sklearn import preprocessing
label_encoder = preprocessing.LabelEncoder()
datainput['gender'] = label_encoder.fit_transform(datainput['gender'])
datainput['Partner'] = label_encoder.fit_transform(datainput['Partner'])
datainput['Dependents'] = label_encoder.fit_transform(datainput['Dependents'])
datainput['PhoneService'] = label_encoder.fit_transform(datainput['PhoneService'])
datainput['MultipleLines'] = label_encoder.fit_transform(datainput['MultipleLines'])
datainput['InternetService'] = label_encoder.fit_transform(datainput['InternetService'])
datainput['OnlineSecurity'] = label_encoder.fit_transform(datainput['OnlineSecurity'])
datainput['OnlineBackup'] = label_encoder.fit_transform(datainput['OnlineBackup'])
datainput['DeviceProtection'] = label_encoder.fit_transform(datainput['DeviceProtection'])
datainput['TechSupport'] = label_encoder.fit_transform(datainput['TechSupport'])
datainput['StreamingTV'] = label_encoder.fit_transform(datainput['StreamingTV'])
datainput['StreamingMovies'] = label_encoder.fit_transform(datainput['StreamingMovies'])
datainput['Contract'] = label_encoder.fit_transform(datainput['Contract'])
datainput['PaperlessBilling'] = label_encoder.fit_transform(datainput['PaperlessBilling'])
datainput['PaymentMethod'] = label_encoder.fit_transform(datainput['PaymentMethod'])
datainput['Churn'] = label_encoder.fit_transform(datainput['Churn'])
#print("input data after label encoder :\n",datainput)

#separating features(X) and label(y)
datainput["Churn"] = datainput["Churn"].astype(int)
Y = datainput["Churn"].values
X = datainput.drop(labels = ["Churn"],axis = 1)

#train_test_split method
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)

#LogisticRegression
classifier=LogisticRegression()
classifier.fit(X_train,Y_train)
Y_pred=classifier.predict(X_test)

#Accuracy
LR = metrics.accuracy_score(Y_test, Y_pred) * 100
print("\nThe accuracy score using the LR is -> ",LR)

#confusion matrix
cm=confusion_matrix(Y_test,Y_pred)
print("\nconfusion matrix : \n",cm)

出力結果

上記のコードを実行すると、次のような結果が得られます。

The accuracy score using the LR is -> 80.8374733853797
confusion matrix :
   [[928 109]
   [161 211]]

この結果から、ロジスティック回帰モデルの正解率は約80.8%であり、混同行列でも真陽性・真陰性の数が偽陽性・偽陰性を大きく上回っていることがわかります。

各変数の重みの確認

最後に、各フィールド(変数)が離反値にどの程度影響を与えているかを評価します。これにより、離反への影響が大きい特定の変数を特定し、顧客離反を防止するための対策を重点的に講じることが可能になります。具体的には、分類器の係数を取得し、各変数の重みを算出します。

コード例

import pandas as pd
import warnings
warnings.filterwarnings("ignore")
from sklearn.linear_model import LogisticRegression

#Loading the dataset with pandas
datainput = pd.read_csv('E:\\Telecom_customers.csv')

datainput.drop(['customerID'], axis=1, inplace=True)
datainput.pop('TotalCharges')
datainput['OnlineBackup'].unique()

#LabelEncoder()
from sklearn import preprocessing
label_encoder = preprocessing.LabelEncoder()
datainput['gender'] = label_encoder.fit_transform(datainput['gender'])
datainput['Partner'] = label_encoder.fit_transform(datainput['Partner'])
datainput['Dependents'] = label_encoder.fit_transform(datainput['Dependents'])
datainput['PhoneService'] = label_encoder.fit_transform(datainput['PhoneService'])
datainput['MultipleLines'] = label_encoder.fit_transform(datainput['MultipleLines'])
datainput['InternetService'] = label_encoder.fit_transform(datainput['InternetService'])
datainput['OnlineSecurity'] = label_encoder.fit_transform(datainput['OnlineSecurity'])
datainput['OnlineBackup'] = label_encoder.fit_transform(datainput['OnlineBackup'])
datainput['DeviceProtection'] = label_encoder.fit_transform(datainput['DeviceProtection'])
datainput['TechSupport'] = label_encoder.fit_transform(datainput['TechSupport'])
datainput['StreamingTV'] = label_encoder.fit_transform(datainput['StreamingTV'])
datainput['StreamingMovies'] = label_encoder.fit_transform(datainput['StreamingMovies'])
datainput['Contract'] = label_encoder.fit_transform(datainput['Contract'])
datainput['PaperlessBilling'] = label_encoder.fit_transform(datainput['PaperlessBilling'])
datainput['PaymentMethod'] = label_encoder.fit_transform(datainput['PaymentMethod'])
datainput['Churn'] = label_encoder.fit_transform(datainput['Churn'])
#print("input data after label encoder :\n",datainput)

#separating features(X) and label(y)
datainput["Churn"] = datainput["Churn"].astype(int)
Y = datainput["Churn"].values
X = datainput.drop(labels = ["Churn"],axis = 1)
#
#train_test_split method
from sklearn.model_selection import train_test_split
X_train, X_test, Y_train, Y_test = train_test_split(X, Y, test_size=0.2)
#
#LogisticRegression
classifier=LogisticRegression()
classifier.fit(X_train,Y_train)
Y_pred=classifier.predict(X_test)

#weights of all the variables
wt = pd.Series(classifier.coef_[0], index=X.columns.values)
print("\nweight of all the variables :")
print(wt.sort_values(ascending=False))

出力結果

上記のコードを実行すると、次のような結果が得られます。

weight of all the variables :
PaperlessBilling      0.389379
SeniorCitizen         0.246504
InternetService       0.209283
Partner               0.067855
StreamingMovies       0.054309
MultipleLines         0.042330
PaymentMethod         0.039134
MonthlyCharges        0.027180
StreamingTV          -0.008606
gender               -0.029547
tenure               -0.034668
DeviceProtection     -0.052690
OnlineBackup         -0.143625
Dependents           -0.209667
OnlineSecurity       -0.245952
TechSupport          -0.254740
Contract             -0.729557
PhoneService         -0.950555
dtype: float64

まとめ

重みの結果を見ると、正の値が大きい「PaperlessBilling(ペーパーレス請求)」「SeniorCitizen(高齢者)」「InternetService(インターネットサービス)」などの変数は離反リスクを高める方向に作用している一方、「PhoneService(電話サービス)」「Contract(契約形態)」「TechSupport(テクニカルサポート)」などの負の値が大きい変数は離反を抑制する方向に働いていることがわかります。特に長期契約やテクニカルサポートの提供が顧客維持に大きく寄与する傾向が読み取れます。

このように、Pythonとscikit-learnを活用すれば、顧客離反の予測モデルを比較的簡単に構築できます。得られた知見をもとに、離反リスクの高い顧客へ先手を打ったアプローチを行うことで、顧客維持率の向上と収益改善につなげることができるでしょう。

  1. Pythonで身につける統計的思考 ― グラフとチャートによるデータ分析入門

    統計学は、機械学習(ML)やAIを学ぶうえで欠かせない基礎知識です。これらの技術分野ではPythonが事実上の標準言語となっているため、統計分析を取り入れたPythonプログラムの書き方をマスターすることが重要になります。本記事では、さまざまなPythonライブラリを活用してグラフやチャートを作成する方法を解説します。多様なチャートを使いこなせるようになると、データを素早く分析し、結論を視覚的に導き出せるようになります。 データの準備 ここでは、さまざまな種子(シード)に関するデータを含むデータセットを使用します。このデータセットはKaggleから入手でき、URLは後述のサンプルコード内に記載

  2. Pythonで国勢調査データを分析する方法|インドの人口統計データを可視化してみよう

    国勢調査(センサス)とは、特定の対象人口に関する情報を体系的に記録・収集する取り組みです。収集されるデータには、人口統計、経済状況、居住環境など、さまざまなカテゴリの情報が含まれています。これらのデータは、政府が現状を正確に把握し、将来に向けた政策立案を行ううえで重要な基礎資料となります。本記事では、Pythonを活用してインドの国勢調査データを分析する方法を解説します。人口動態や経済指標など複数の観点からデータを掘り下げ、その結果をグラフとして視覚的に表現します。使用するデータセットはKaggleから入手した「India Districts Census 2011」です。データの準備と読み込