1. 如何预测客户是否流失?(留存预估)
拉新
留存
流失 VS 留存?
参考用户的一些历史(业务)行为
人口统计学信息(user表中):年龄、性别、学历、婚姻……
业务信息:购买记录、浏览记录……
先去发育一段时间,搞一些原始的累积
历史数据:大量留存的用户 + 大量流失的用户
有了这些历史数据,才能谈优化发展。
预测:二分类即可
根据历史数据,做二分类预测
2. 数据存在的一些疑惑?
拿到数据后,无法判定哪些特征有用,哪些特征没用。
那就都先留着。
对于离散型变量,都是非数字的标准格式。
状态编码
zero index
0 ~ N-1
不会增加特征的维度
但是会潜在引入状态大小问题
本质上:把离散变量当做连续变量处理了。
one hot
state 0: [1,0,0,0,……,0]
state 1: [0,1,0,0,……,0]
state N: [0,0,0,0,……,1]
一个特征变成N个特征
构建了一个稀疏矩阵(大量的0,少数的有用数据)
浪费了存储和计算
特征之间距离相等,互相垂直
连续型变量:
数据级不同,量纲不同
1. 数据读取
"""
1. 数据读取
"""
#导入Pandas库,并将其简称为pd。
#Pandas是一个强大的Python数据分析库,提供了大量的数据处理和分析的工具。
import pandas as pd
#使用Pandas的read_csv函数来读取位于/Users/csblog/信用卡客户流失数据集.csv的CSV文件。
#读取的数据被存储在变量data中,data现在是一个DataFrame对象,
#DataFrame是Pandas中用于存储和操作结构化数据的主要数据结构。
data = pd.read_csv(filepath_or_buffer="/Users/csblog/信用卡客户流失数据集.csv")
#打印出data DataFrame的形状,即它的行数和列数。
#形状以元组(行数, 列数)的形式给出。这对于快速了解数据集的大小非常有用。
data.shape
#打印出data DataFrame的简要摘要,
#包括每列的名称、非空值的数量、数据类型(如整型、浮点型、对象型等),以及内存使用情况。
#这对于了解数据集的基本结构和数据类型的分布很有帮助。
data.info()
#返回data DataFrame的最后3行数据(n=3指定了返回的行数)。
#这对于查看数据集的末尾部分,了解数据集的结束情况或查看最新记录非常有用。
data.tail(n=3)
#从data DataFrame中随机抽取3行数据。
#这对于快速查看数据集的样本或进行随机抽样分析很有用。
#默认情况下,sample方法会返回随机抽取的行,
#但你也可以通过random_state参数指定一个随机种子来确保结果的可重复性。
data.sample(n=3)
#注意:虽然data.tail(n=3)和data.sample(n=3)都返回了数据框的几行,
#但它们的目的不同。tail返回的是数据集末尾的几行,而sample返回的是随机选取的几行。
"""
2. 数据清洗
重复 什么叫重复?user_id? 缺失 异常
"""
# 判断是否有重复
data.duplicated(subset=["CLIENTNUM"]).sum()
#删除重复(保留其中一条)
data.drop_duplicates(subset=["CLIENTNUM"],inplace=True)
data.info()
#用于检查DataFrame data中每一列(或每一行,如果你对行进行操作的话)中缺失值(NaN,即“Not a Number”)的数量
data.isna().sum()
#在Pandas中,data.dropna(how="any", inplace=True)
#是一个用于删除DataFrame data中包含缺失值(NaN)的行或列的操作。
#这里的参数具体说明了如何执行这个删除操作:
#how="any":这个参数指定了当任何一个元素(在行或列中,取决于axis参数,但默认情况下axis=0表示行)#是缺失值时,整行(或列,如果指定了axis=1)就会被删除。
#在这个例子中,因为how="any",所以任何包含至少一个NaN的行都会被删除。
#inplace=True:这个参数指定了操作是否应该直接在原始DataFrame上进行,
#而不是返回一个新的、修改后的DataFrame副本。
#当inplace=True时,原始DataFrame data会被修改,并且这个操作不会返回任何值(即返回None)。
#如果你不想修改原始DataFrame,可以省略这个参数或将其设置为False,这样操作就会返回一个新的#DataFrame副本,而原始DataFrame保持不变。
#因此,data.dropna(how="any", inplace=True)会删除data中所有包含至少一个NaN值的行,并且这个修改会直接反映在原始的data DataFrame上。
#注意:如果你只想删除包含全部NaN值的行(或列),你可以将how参数设置为"all"。但在大多数情况下,#how="any"是删除包含至少一个NaN值的行的常用选择。
#删除CLIENTNUM列
data.drop(columns="CLIENTNUM",inplace=True)
"""
3. 数据预处理
编码问题
异常问题
量纲问题
"""
# 判断Attrition_Flag有几个离散状态
data["Attrition_Flag"].unique()
#构建状态字典
#字典推导式,该字典的键(key)是Attrition_Flag列中的唯一值,
#而值(value)是这些唯一值在枚举中的索引(从0开始)。
#注意:enumerate() 非常适合用于在 for 循环中同时获取索引和值。
#enumerate() 函数返回一个枚举对象,该对象是一个迭代器,它生成由 (index, value) 组成的元组
attrition_dict = {attrition:idx for idx, attrition in enumerate(data["Attrition_Flag"].unique())}
#更新对应的列
data["Attrition_Flag"]=data["Attrition_Flag"].apply(func=lambda ele:attrition_dict[ele])
idx2label = {idx:attrition for attrition,idx in attrition_dict.items()}
"""
3.2, Customer_Age
按位或 |
按位与 &
否定,按位取反 !
"""
#判断有多少个年龄小于0或是大于150岁的异常数据
((data["Customer_Age"] < 0) | (data["Customer_Age"]>150)).sum()
"""
3.3, Gender
"""
# 判断有多少个离散状态
data["Gender"].unique()
#构建状态字典
gender_dict = {gender:idx for idx,gender in enumerate(data["Gender"].unique())}
#更新对应的列
data["Gender"] = data["Gender"].apply(func = lambda ele:gender_dict[ele])
"""
3.5, Education_Level
"""
#判断有多少个离散状态
data["Education_Level"].unique()
#构建状态字典
education_dict = {education:idx for idx,education in enumerate(data["Education_Level"].unique())}
#更新对应列
data["Education_Level"] = data["Education_Level"].apply(func=lambda ele:education_dict[ele])
"""
4,数据拆分
"""
y = data["Attrition_Flag"].to_numpy()
X = data.drop(columns = ["Attrition_Flag"]).to_numpy()
X.shape,y.shape
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,random_state=0)
X_train.shape
y_train.shape
"""
5, 数据标准化处理
"""
#从训练集中抽取预处理参数mu和sigma
#计算数组X沿着指定轴的均值
#axis=0表示沿着数组的第一个轴(通常是行)进行操作,即计算每一列的平均值。
#对于二维数组(矩阵)来说,axis=0会将所有行的对应元素相加,然后除以行数,得到每一列的均值。
mu = X_train.mean(axis=0)
sigma = X_train.std(axis=0)
#执行标准化操作
X_train = (X_train - mu)/sigma
X_test = (X_test - mu)/sigma
"""
6, 数据保存
特征编码时的状态字典
特征的各种异常判断
标准化处理的参数
"""
import joblib
state_dict = [idx2label, gender_dict, education_dict, marital_dict, income_dict, card_dict, mu, sigma]
data = [X_train, y_train, X_test, y_test]
joblib.dump(value=[state_dict, data], filename="all_data.csblog")
2. 建模预测
"""
1. 数据加载
"""
import joblib
[state_dict,data]=joblib.load(filename="all_data.csblog")
idx2label, gender_dict, education_dict, marital_dict, income_dict, card_dict, mu, sigma = state_dict
X_train, y_train, X_test, y_test = data
X_train.shape
"""
2. 尝试所有算法
二分类问题:
算法:
KNN
朴素贝叶斯
决策树
"""
"""
2.1 KNN算法
"""
# 从sklearn的neighbors模块中导入KNeighborsClassifier类,用于实现K最近邻算法。
from sklearn.neighbors import KNeighborsClassifier
# 初始化相关参数
best_n_neighbor = 0
best_acc = 0
best_model = None
# 简单遍历,寻找比较好的 N
for n_neighbor in range(3, 31, 1):
# 创建KNN分类器实例
#n_neighbors=5指定了在预测时使用最近的5个邻居
knn = KNeighborsClassifier(n_neighbors=n_neighbor)
# 使用训练数据训练模型
knn.fit(X=X_train, y=y_train)
# 使用训练好的模型对测试集进行预测
y_pred = knn.predict(X=X_test)
acc = (y_pred == y_test).mean()
if acc > best_acc:
best_n_neighbor = n_neighbor
best_acc = acc
best_model = knn
print(f"找到一个更好的模型: {best_n_neighbor}, {best_acc}")
# 保存模型
joblib.dump(value=[best_n_neighbor, best_acc, best_model], filename="best_knn.csblog")
"""
2.2 朴素贝叶斯
高斯朴素贝叶斯
高斯:假定所有的特征都是连续型的
"""
from sklearn.naive_bayes import GaussianNB
gnb = GaussianNB()
gnb.fit(X=X_train, y=y_train)
y_pred = gnb.predict(X=X_test)
acc = (y_pred == y_test).mean()
acc
"""
2.3 决策树
"""
from sklearn.tree import DecisionTreeClassifier
dtc = DecisionTreeClassifier()
dtc.fit(X=X_train, y=y_train)
y_pred = dtc.predict(X=X_test)
acc = (y_pred == y_test).mean()
acc
3. 推理过程
从原始数据开始推理
x = "709967358,48,M,4,Post-Graduate,Single,$80K - $120K,Blue,36,6,2,3,30367,2362,28005,1.708,1671,27,0.929,0.078"
def predict(x):
"""
推理函数
"""
import numpy as np
# print(f"原始输入:{x}")
x = x.split(",")[1:]
# print(f"切分之后:{x}")
temp = []
# 1, age
temp.append(float(x[0]))
# 2, gender
temp.append(gender_dict[x[1]])
# 3,
temp.append(float(x[2]))
# 4,
temp.append(education_dict[x[3]])
# 5,
temp.append(marital_dict[x[4]])
# 6,
temp.append(income_dict[x[5]])
# 7,
temp.append(card_dict[x[6]])
# 8 - 19
temp.extend([float(ele) for ele in x[7: ]])
# print(f"编码之后:{temp}")
# 标准化之后
x = np.array(temp)
x = (x - mu) / sigma
# print(f"标准化之后:{x}")
# 模型推理
y_pred = dtc.predict(X=[x])
# print(f"推理结果:{y_pred}")
# 结果解析
final_result = idx2label[y_pred[0]]
# print(f"最终结果:{final_result}")
return final_result
服务端 C/S 架构部署
- FastAPI web
4. 扩展
评价指标
准确率 Accuracy