一、读取数据
import pandas as pd
import numpy as np
df = pd.read_csv('E:/workspace/dataset/WA_Fn-UseC_-Telco-Customer-Churn.csv')
df.head()
字段解释:
二、数据质量探索
1、去重分析
df1 = df.copy()
# 判断是否存在重复
df1['customerID'].nunique() == df1.shape[0]
# 去重
df1.drop_duplicates()
2、缺失占比分析
# 缺失值占比分析
def missing(df):
# 缺失数量统计
missing_number = df1.isnull().sum().sort_values(ascending=False)
# 缺失占比
missing_percent = (df1.isnull().sum()/df1.count()).sort_values(ascending=False)
missing_values = pd.concat([missing_number, missing_percent], axis=1, keys=['Missing_number', 'Missing_percent'])
return missing_values
missing(df1)
3、划分字段类型:离散数据、连续数据、标签
df1.nunique()
id = ['customerID']
# 离散字段
category_cols = ['gender', 'SeniorCitizen', 'Partner', 'Dependents', 'tenure',
'PhoneService', 'MultipleLines', 'InternetService', 'OnlineSecurity', 'OnlineBackup',
'DeviceProtection', 'TechSupport', 'StreamingTV', 'StreamingMovies', 'Contract', 'PaperlessBilling',
'PaymentMethod']
# 连续字段
numeric_cols = ['tenure', 'MonthlyCharges', 'TotalCharges']
# 标签
target = 'Churn'
# 验证是否划分能完全
print(len(id) + len(category_cols) + len(numeric_cols) + 1 == df1.shape[1])
3.1 离散值字段分析
df1[category_cols].nunique()
for feature in category_cols:
print(f'{feature}:{df1[feature].unique()}')
3.2 连续值字段分析
(1)检查是否存在缺失
对于连续变量是否存在其他值表示缺失值的情况,可以先转化为数值变量再进行分析,例如如果是用空格代表缺失值
# 检查空格第一次出现位置的索引,没有则返回-1
def find_index(data_col, val):
"""
查询某值在某列中第一次出现位置的索引,没有则返回-1
:param data_col: 查询的列
:param val: 具体取值
"""
val_list = [val]
if data_col.isin(val_list).sum() == 0:
index = -1
else:
index = data_col.isin(val_list).idxmax()
return index
# 查看空格第一次出现在哪一列的哪个位置:
for col in numeric_cols:
print(f"{col}:", find_index(df1[col], ' '))
(2)缺失值用np.nan填充,并将连续字段转换为浮点类型
# 使用np.nan对空格进行替换,并将’MonthlyCharges’转化为浮点数类型
def to_float(df, numeric_cols):
for col in numeric_cols:
df[col] = df[col].apply(lambda x: x if x!=' ' else np.nan).astype(float)
to_float(df1, numeric_cols)
再次查看缺失情况
4、缺失值处理
缺失值处理方法:删除、均值、中位数、众数、特殊值、随机值填充等
4.1 删除
# 删除:当缺失数据所占比例较大
df2 = df1
df2.dropna(axis=0, how='any', subset=['TotalCharges'])
4.2 缺失值填充
df3 = df1.copy()
mean_value = df3['TotalCharges'].mean() # 均值
median_value = df3['TotalCharges'].median() # 中位数
mode_value = df3['TotalCharges'].mode() # 众数
random_value = np.random.uniform(df3['TotalCharges'].min(), df3['TotalCharges'].max())
df3['TotalCharges'].fillna(mean_value, inplace=True)
5、异常值分析
import seaborn as sns
import matplotlib.pyplot as plt
import math
# 箱线图
def draw_boxplot(cols):
fig = plt.figure(figsize=(16, 6), dpi=200)
i = 1
for col in cols:
plt.subplot(math.ceil(len(cols)/2), 2, i)
plt.boxplot(df3[col])
plt.xlabel(col)
i += 1
draw_boxplot(numeric_cols)
def abnormal_data(df, col):
"""
求极值
"""
Q3 = df[col].describe()['75%']
Q1 = df[col].describe()['25%']
IQR = Q3-Q1
return (Q1-1.5*IQR, Q3+1.5*IQR)
for col in numeric_cols:
min_max = abnormal_data(df3, col)
print(min_max)
还可以通过连续变量的分布情况来观察是否存在异常值
import warnings
warnings.filterwarnings('ignore')
%matplotlib inline
def draw_displot(df, cols):
fig = plt.figure(figsize=(14,4), dpi=200)
for col in cols:
sns.displot(df[col], kde=True)
draw_displot(df3, numeric_cols)
6、变量相关性探索分析
连续变量之间相关性可以使用皮尔逊相关系数;
连续变量和离散变量之间相关性则可以卡方检验;
离散变量之间则可以从信息增益;
可以忽略变量连续/离散特性,统一使用相关系数。
# 剔除id列
df3 = df3.iloc[:, 1:].copy()
df4 = df3.copy()
# 将标签Yes/No转化为1/0
df4['Churn'].replace(to_replace='Yes', value=1, inplace=True)
df4['Churn'].replace(to_replace='No', value=0, inplace=True)
df4.head()
# 将其他分类变量转化为哑变量,连续变量保持不变
df_dummies = pd.get_dummies(df4)
df_dummies.head()
# 计算和标签之间的相关性矩阵
df_dummies.corr()
plt.figure(figsize=(18, 10), dpi=200)
sns.heatmap(df_dummies.corr())
df_dummies.corr()['Churn'].sort_values(ascending=False)[1:].plot(kind='bar')
# 对比不同字段不同取值下流失用户的占比情况
# 柱状图
fig = plt.figure(figsize=(8,5), dpi=100)
fig.add_subplot(121)
sns.countplot(x='gender', hue='Churn', data=df4, palette='Blues', dodge=True)
plt.xlabel("Gender")
plt.title("Churn by Gender")
fig.add_subplot(122)
# 柱状堆叠图(堆叠图简单理解其实就是纯粹的重合,并不是上下堆叠,而是深色柱状图覆盖在浅色柱状图的上面。)
sns.countplot(x="gender",hue="Churn",data=df4, palette="Blues", dodge=False)
plt.xlabel("Gender")
plt.title("Churn by Gender")
三、特征编码
3.1 离散特征编码
(1)类别编码-独热编码
实现方式1: pandas.get_dummies(df, columns=['education', 'PaymentMethod'])
实现方式2: sklearn的
preprocessing.OneHotEncoder(drop='if_binary').fit_transform(df[col])
pd.get_dummies(df, columns=['MultipleLines', 'PaymentMethod'])
from sklearn import preprocessing
from sklearn.preprocessing import LabelEncoder
from sklearn.preprocessing import OneHotEncoder
from category_encoders import OrdinalEncoder
preprocessing.OneHotEncoder(drop='if_binary').fit_transform(df[['MultipleLines', 'PaymentMethod']]).toarray()
(2)类别编码-无序编码
实现方式1:pandas.factorize(df['country'])[0]
实现方式2:sklearn的
preprocessing.LabelEncoder().fit_transform(df['MultipleLines'])
pd.factorize(df['MultipleLines'])[0]
LabelEncoder().fit_transform(df['MultipleLines'])
(3)类别编码-有序编码
实现方式1: 手动 df['data'].map({'one year': 1, 'two year':2})
实现方式2:from category_encoders import OrdinalEncoder
OrdinalEncoder().fit_transform(df[['MultipleLines', 'PaymentMethod']])
df['MultipleLines'].map({'No phone service': 1, 'Yes':2, 'No':3})
OrdinalEncoder().fit_transform(df[['MultipleLines', 'PaymentMethod']])
3.2 连续特征编码
1、数值缩放:标准化、归一化、L1/L2正则化、robust_scale、取对数log、softmax等
2、连续特征离散化:
(1)无监督分箱(等频、等距、Kmeans聚类分箱)
(2)有监督分箱
ps:树模型不需要:标准化、归一化
(1)数值缩放
# Z-score标准化(也称为标准差标准化)
# (x-均值)/标准差
from sklearn.preprocessing import StandardScaler
std_scaler = StandardScaler()
std_scaler.fit_transform(df2[['MonthlyCharges']])
# 幅度缩放:最大最小值缩放到[0, 1]区间内
# (x-min)/(max-min)
from sklearn.preprocessing import MinMaxScaler
mm_scaler = MinMaxScaler()
mm_scaler.fit_transform(df2[['MonthlyCharges']])
# 归一化方法1
from sklearn.preprocessing import normalize
normalize(df2[['MonthlyCharges']], norm='l1') # L1范数
# 归一化方法2
from sklearn.preprocessing import Normalizer
normalizer = Normalizer(norm='l2')
normalizer.fit_transform(df2[['MonthlyCharges']])
# 中位数或者四分位数去中心化数据,对异常值不敏感
# 计算其中位数、四分位数范围:IQR = 上四分位数Q75-下四分位数Q25的差
# 将每个特征的值减去中位数,再除以四分位数范围,得到标准化后的值。
# (x-中位数)/IQR
from sklearn.preprocessing import robust_scale
robust_scale(df2[['MonthlyCharges']])
# 取对数变换
df2[['MonthlyCharges']].apply(lambda x: np.log(x))
(2)连续特征离散化-无监督分箱
# 等距切分
pd.cut(df2['MonthlyCharges'], 10)
# 等频切分
pd.qcut(df2['MonthlyCharges'], 10)
# 等距/等距/kmeans分箱
from sklearn.preprocessing import KBinsDiscretizer
# strategy:取值 uniform等距分箱;quantile等频分箱;kmeans分箱
bins = KBinsDiscretizer(n_bins=10, encode='ordinal', strategy='uniform')
bins.fit_transform(df2[['MonthlyCharges']])
# 显示分箱范围
bins.bin_edges_
# Kmeans分箱
from sklearn import cluster
kmeans = cluster.KMeans(n_clusters=3)
kmeans.fit_transform(df2[['MonthlyCharges']])
# 分箱结果
kmeans.labels_
(3)连续特征离散化-有监督分箱
# 数据和标签之间的关系进行数据挖掘
from sklearn import tree
income = np.array([0, 10, 180, 30, 55, 35, 25, 75, 80, 10]).reshape(-1, 1)
y = np.array([1, 1, 0, 1, 0, 0, 0, 1, 0, 0])
clf = tree.DecisionTreeClassifier().fit(income, y)
plt.figure(figsize=(6, 2), dpi=150)
tree.plot_tree(clf)
# 预测(分箱)
clf.predict(income)
四、案例-逻辑回归训练模型
df2 = df.copy()
# 连续值转化为float类型,' '处理
df2 = to_float(df2, numeric_cols)
# 离散值编码
df_category = one_hot(df2, category_cols)
# 连续值编码
df_numeric = pre_bins(df2, numeric_cols, 'uniform')
# 目标编码
df_target = pre_target(df, target)
X = pd.concat([df_category, df_numeric], axis=1)
Y = df_target
from sklearn.metrics import accuracy_score, recall_score, precision_score, f1_score, roc_auc_score
from sklearn.model_selection import train_test_split
from sklearn.linear_model import LogisticRegression
from sklearn.pipeline import make_pipeline
from sklearn.compose import ColumnTransformer
from sklearn import preprocessing
# 划分数据集
X_train, X_test, y_train, y_test = train_test_split(X, Y,test_size=0.25, random_state=1, shuffle=True)
# X:所要划分的样本特征集
# Y:所要划分的样本结果
# test_size:样本占比,如果是整数的话就是样本的数量
# random_state:是随机数的种子。
# 随机数种子:其实就是该组随机数的编号,在需要重复试验的时候,保证得到一组一样的随机数。比如你每次都填1,其他参数一样的情况下你得到的随机数组是一样的。但填0或不填,每次都会不一样。
# shuffle:默认为True,
# stratify:参数用于确保划分后每个子集中的类别比例与原始数据集一致。
# 实例化逻辑回归
clf = LogisticRegression(max_iter=int(1e8))
# 模型训练
model = clf.fit(X_train, y_train)
# 模型效果
model.score(X_test, y_test)