机器学习/数据分析案例---学生消费行为分析,“泰迪杯赛题”
前言
-
这是一道“泰迪杯“杯的数据分析赛题,对于我现阶段,难度还是很大的,参考了不少资料做完的,这一次我再一次意思到了自己的实力🌵🌵🌵🌵🌵🌵;
-
熟练使用Pandas、机器学习库是基础😢😢😢😢😢😢😢;
-
我感觉数据分析具有很强的业务性,很适合学生去学习,从而了解业务;
-
这一次代码量很大,观看起来应该都很麻烦,但是总体坐下来对数据分析逻辑也有了不小帮助;
-
欢迎收藏 + 关注,本人将会持续更新,未来将每周至少更新一篇机器学习、深度学习案例。
思路分析
Part1 数据导入与预处理
任务1.1
- 对数据data1、data2、data3中数据进行探究,理解字段含义
- 处理缺失值和异常值
- 对特殊字段,进行初步分析
- 处理后数据重新保存成.csv文件
# 导入库
import numpy as np
import pandas as pd
import os # 读取数据
data1 = pd.read_csv("data1.csv", encoding="gbk")
data2 = pd.read_csv("data2.csv", encoding="gbk")
data3 = pd.read_csv("data3.csv", encoding="gbk")
data1的处理
# data1的展示
data1.head(3)
Index | CardNo | Sex | Major | AccessCardNo | |
---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 |
1 | 2 | 180002 | 男 | 18国际金融 | 20521594 |
2 | 3 | 180003 | 男 | 18国际金融 | 20513946 |
# 查看数据大小
data1.shape
输出:
(4341, 5)
# 标题栏从新命名,命名为中文
data1.columns = ['序号', '校园卡号', '性别', '专业名称', '门禁卡号']
# 效果输出
data1.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | |
---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 |
1 | 2 | 180002 | 男 | 18国际金融 | 20521594 |
2 | 3 | 180003 | 男 | 18国际金融 | 20513946 |
# 查看数据类型,看是否有缺失值,方便后续处理
data1.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 4341 entries, 0 to 4340
Data columns (total 5 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 序号 4341 non-null int64 1 校园卡号 4341 non-null int64 2 性别 4341 non-null object3 专业名称 4341 non-null object4 门禁卡号 4341 non-null int64
dtypes: int64(3), object(2)
memory usage: 169.7+ KB
# 没有缺失值,data1数据主要就是记录学生信息,故没有异常值
data1.to_csv("./task1_1_1.csv", index=False, encoding='utf-8-sig')
data2的处理
# 查看前几条数据
data2.head(3)
Index | CardNo | PeoNo | Date | Money | FundMoney | Surplus | CardCount | Type | TermNo | TermSerNo | conOperNo | OperNo | Dept | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 117342773 | 181316 | 20181316 | 2019/4/20 20:17 | 3.0 | 0.0 | 186.1 | 818 | 消费 | 49 | NaN | NaN | 235 | 第一食堂 |
1 | 117344766 | 181316 | 20181316 | 2019/4/20 8:47 | 0.5 | 0.0 | 199.5 | 814 | 消费 | 63 | NaN | NaN | 27 | 第二食堂 |
2 | 117346258 | 181316 | 20181316 | 2019/4/22 7:27 | 0.5 | 0.0 | 183.1 | 820 | 消费 | 63 | NaN | NaN | 27 | 第二食堂 |
# 查看数据维度,大小
data2.shape # 数据量很大
输出:
(519367, 14)
# 表头修改,方便深入理解
data2.columns = ['流水号', '校园卡号', '校园卡编号', '消费时间', '消费金额', '存储金额', '余额', '消费次数', '消费类型', '消费项目编码', '消费项目序列号', '消费操作编码', '操作编码', '消费地点']
# 查看数据
data2.head(3)
流水号 | 校园卡号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 消费项目序列号 | 消费操作编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 117342773 | 181316 | 20181316 | 2019/4/20 20:17 | 3.0 | 0.0 | 186.1 | 818 | 消费 | 49 | NaN | NaN | 235 | 第一食堂 |
1 | 117344766 | 181316 | 20181316 | 2019/4/20 8:47 | 0.5 | 0.0 | 199.5 | 814 | 消费 | 63 | NaN | NaN | 27 | 第二食堂 |
2 | 117346258 | 181316 | 20181316 | 2019/4/22 7:27 | 0.5 | 0.0 | 183.1 | 820 | 消费 | 63 | NaN | NaN | 27 | 第二食堂 |
# data2中消费时间数据进行格式转换,采用自动推断格式
data2.loc[:, '消费时间'] = pd.to_datetime(data2.loc[:, '消费时间'], format='mixed', errors='coerce')
data2.head(3)
流水号 | 校园卡号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 消费项目序列号 | 消费操作编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 117342773 | 181316 | 20181316 | 2019-04-20 20:17:00 | 3.0 | 0.0 | 186.1 | 818 | 消费 | 49 | NaN | NaN | 235 | 第一食堂 |
1 | 117344766 | 181316 | 20181316 | 2019-04-20 08:47:00 | 0.5 | 0.0 | 199.5 | 814 | 消费 | 63 | NaN | NaN | 27 | 第二食堂 |
2 | 117346258 | 181316 | 20181316 | 2019-04-22 07:27:00 | 0.5 | 0.0 | 183.1 | 820 | 消费 | 63 | NaN | NaN | 27 | 第二食堂 |
# 查看缺失值占比
data2.apply(lambda x : sum(x.isnull()) / len(x), axis=0)
输出:
流水号 0.000000
校园卡号 0.000000
校园卡编号 0.000000
消费时间 0.000000
消费金额 0.000000
存储金额 0.000000
余额 0.000000
消费次数 0.000000
消费类型 0.000000
消费项目编码 0.000000
消费项目序列号 0.986020
消费操作编码 0.999517
操作编码 0.000000
消费地点 0.000000
dtype: float64
消费项目序列号和操作编码缺失值过多,故删除
data2_new = data2[['流水号', '校园卡号', '校园卡编号', '消费时间', '消费金额', '存储金额', '余额', '消费次数', '消费类型', '消费项目编码', '操作编码', '消费地点']]
data2_new.head(3)
流水号 | 校园卡号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 117342773 | 181316 | 20181316 | 2019-04-20 20:17:00 | 3.0 | 0.0 | 186.1 | 818 | 消费 | 49 | 235 | 第一食堂 |
1 | 117344766 | 181316 | 20181316 | 2019-04-20 08:47:00 | 0.5 | 0.0 | 199.5 | 814 | 消费 | 63 | 27 | 第二食堂 |
2 | 117346258 | 181316 | 20181316 | 2019-04-22 07:27:00 | 0.5 | 0.0 | 183.1 | 820 | 消费 | 63 | 27 | 第二食堂 |
# 统计消费地点出现的频次,为了后面分析消费行为
data2['消费地点'].value_counts(dropna=False)
输出:
消费地点
第二食堂 154873
第五食堂 117615
第一食堂 62090
第四食堂 60841
第三食堂 52103
好利来食品店 31781
财务处 18295
红太阳超市 12942
水电缴费处 3388
教师食堂 2145
医务室 794
第二图书馆 376
第一图书馆 291
工商系部 197
自然科学书库 195
财务部 170
第七教学楼 165
基础课部 133
艺术设计学院 131
第六教学楼 130
人文社科 109
第二教学楼 99
第五教学楼 99
飞凤轩宿管办 86
机电系 78
第四教学楼 56
第三教学楼 52
宿管办 47
青鸾苑宿管办 25
财经系 23
第一教学楼 23
外语系 12
旅游系 3
Name: count, dtype: int64
# 统计量分析,选取相关的数据特征
data2[['消费金额', '存储金额', '余额', '消费次数']].describe()
消费金额 | 存储金额 | 余额 | 消费次数 | |
---|---|---|---|---|
count | 519367.000000 | 519367.000000 | 519367.000000 | 519367.000000 |
mean | 4.087279 | 3.949758 | 78.495517 | 1016.565421 |
std | 8.900284 | 34.329017 | 121.537938 | 812.625096 |
min | 0.000000 | 0.000000 | 0.000000 | 1.000000 |
25% | 1.200000 | 0.000000 | 34.100000 | 504.000000 |
50% | 3.000000 | 0.000000 | 64.500000 | 749.000000 |
75% | 6.000000 | 0.000000 | 99.390000 | 1422.000000 |
max | 900.000000 | 9800.000000 | 9903.610000 | 14575.000000 |
# 存储新的cvs文件中
data2_new.to_csv('./task1_1_2.csv', index=False, encoding='utf-8-sig')
data3处理
# 查看data3数据
data3.head(3)
Index | AccessCardNo | Date | Address | Access | Describe | |
---|---|---|---|---|---|---|
0 | 1330906 | 25558880 | 2019/4/1 0:00 | 第六教学楼[进门] | 1 | 允许通过 |
1 | 1330907 | 18413143 | 2019/4/1 0:02 | 第六教学楼[出门] | 1 | 允许通过 |
2 | 1331384 | 11642752 | 2019/4/1 0:00 | 飞凤轩[进门] | 1 | 允许通过 |
# 查看维度
data3.shape
输出:
(43156, 6)
# 重新命名
data3.columns = ['序号', '门禁卡号', '进出时间', '进出地点', '是否通过', '描述']
data3.head(3)
序号 | 门禁卡号 | 进出时间 | 进出地点 | 是否通过 | 描述 | |
---|---|---|---|---|---|---|
0 | 1330906 | 25558880 | 2019/4/1 0:00 | 第六教学楼[进门] | 1 | 允许通过 |
1 | 1330907 | 18413143 | 2019/4/1 0:02 | 第六教学楼[出门] | 1 | 允许通过 |
2 | 1331384 | 11642752 | 2019/4/1 0:00 | 飞凤轩[进门] | 1 | 允许通过 |
# 查看数据类型
data3.dtypes
输出:
序号 int64
门禁卡号 int64
进出时间 object
进出地点 object
是否通过 int64
描述 object
dtype: object
# 修改时间代码
data3['进出时间'] = pd.to_datetime(data3.loc[:, '进出时间'], format='mixed', errors='coerce')
# 查看
data3.head(3)
序号 | 门禁卡号 | 进出时间 | 进出地点 | 是否通过 | 描述 | |
---|---|---|---|---|---|---|
0 | 1330906 | 25558880 | 2019-04-01 00:00:00 | 第六教学楼[进门] | 1 | 允许通过 |
1 | 1330907 | 18413143 | 2019-04-01 00:02:00 | 第六教学楼[出门] | 1 | 允许通过 |
2 | 1331384 | 11642752 | 2019-04-01 00:00:00 | 飞凤轩[进门] | 1 | 允许通过 |
# 统计出现各地点的频次
data3['进出地点'].value_counts(dropna=False)
进出地点
飞凤轩[进门] 10689
飞凤轩[出门] 10397
第六教学楼[进门] 7713
第六教学楼[出门] 7217
青鸾苑[出门] 3318
青鸾苑[进门] 2787
第七教学楼[进门] 449
第五教学楼[进门] 250
第五教学楼[出门] 222
第七教学楼[出门] 114
Name: count, dtype: int64
# 统计进出成功
data3['是否通过'].value_counts(dropna=False)
输出:
是否通过
1 41749
0 1407
Name: count, dtype: int64
# 0,代表没有进出,故删除
print('删除异常值之前: ', data3.shape)
data3 = data3[data3.loc[:, '是否通过'] != 0]
print('删除异常值之后: ', data3.shape)
删除异常值之前: (43156, 6)
删除异常值之后: (41749, 6)
# 存储
data3.to_csv('./task1_1_3.csv', index=False, encoding='utf-8-sig')
任务1.2
- 分别对data1个人信息与data2消费记录,data3的门禁出入建立关联
data1 = pd.read_csv("./task1_1_1.csv")
data2 = pd.read_csv("./task1_1_2.csv")
data3 = pd.read_csv("./task1_1_3.csv")data1.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | |
---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 |
1 | 2 | 180002 | 男 | 18国际金融 | 20521594 |
2 | 3 | 180003 | 男 | 18国际金融 | 20513946 |
data2.head(3)
流水号 | 校园卡号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 117342773 | 181316 | 20181316 | 2019-04-20 20:17:00 | 3.0 | 0.0 | 186.1 | 818 | 消费 | 49 | 235 | 第一食堂 |
1 | 117344766 | 181316 | 20181316 | 2019-04-20 08:47:00 | 0.5 | 0.0 | 199.5 | 814 | 消费 | 63 | 27 | 第二食堂 |
2 | 117346258 | 181316 | 20181316 | 2019-04-22 07:27:00 | 0.5 | 0.0 | 183.1 | 820 | 消费 | 63 | 27 | 第二食堂 |
data3.head(3)
序号 | 门禁卡号 | 进出时间 | 进出地点 | 是否通过 | 描述 | |
---|---|---|---|---|---|---|
0 | 1330906 | 25558880 | 2019-04-01 00:00:00 | 第六教学楼[进门] | 1 | 允许通过 |
1 | 1330907 | 18413143 | 2019-04-01 00:02:00 | 第六教学楼[出门] | 1 | 允许通过 |
2 | 1331384 | 11642752 | 2019-04-01 00:00:00 | 飞凤轩[进门] | 1 | 允许通过 |
# data1 和 data2 进行合并
data1_merge_data2 = pd.merge(data1, data2, how='left', left_on='校园卡号', right_on='校园卡号')
data1_merge_data2.shape
输出:
(242152, 16)
data1_merge_data2.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117331517.0 | 20181.0 | 2019-04-21 18:30:00 | 7.0 | 0.0 | 28.4 | 206.0 | 消费 | 41.0 | 249.0 | 第四食堂 |
1 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117341866.0 | 20181.0 | 2019-04-22 09:40:00 | 3.5 | 0.0 | 24.9 | 207.0 | 消费 | 19.0 | 236.0 | 第一食堂 |
2 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117154618.0 | 20181.0 | 2019-04-10 16:42:00 | 11.0 | 0.0 | 2.7 | 189.0 | 消费 | 82.0 | 18.0 | 第四食堂 |
# 合并后数据分析,检查数据确实
data1_merge_data2.apply(lambda x : sum(x.isnull()) / len(x), axis=0)
序号 0.000000
校园卡号 0.000000
性别 0.000000
专业名称 0.000000
门禁卡号 0.000000
流水号 0.004431
校园卡编号 0.004431
消费时间 0.004431
消费金额 0.004431
存储金额 0.004431
余额 0.004431
消费次数 0.004431
消费类型 0.004431
消费项目编码 0.004431
操作编码 0.004431
消费地点 0.004431
dtype: float64
缺失值占比很小,数据量足够大,故删除
print('删除前缺失值: ', data1_merge_data2.shape)
data1_merge_data2 = data1_merge_data2.dropna(subset=['消费地点'], how='any')
print('删除缺失值后:', data1_merge_data2.shape)
删除前缺失值: (242152, 16)
删除缺失值后: (241079, 16)
# 合并后数据进行存储
data1_merge_data2.to_csv('./task1_2_1.csv', index=False, encoding='utf-8-sig')
# 合并data1和data3数据
data1_merge_data3 = pd.merge(data1, data3, how='left', left_on='门禁卡号', right_on='门禁卡号')
data1_merge_data3.head()
序号_x | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 序号_y | 进出时间 | 进出地点 | 是否通过 | 描述 | |
---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 | NaN | NaN | NaN | NaN | NaN |
1 | 2 | 180002 | 男 | 18国际金融 | 20521594 | NaN | NaN | NaN | NaN | NaN |
2 | 3 | 180003 | 男 | 18国际金融 | 20513946 | NaN | NaN | NaN | NaN | NaN |
3 | 4 | 180004 | 男 | 18国际金融 | 20018058 | NaN | NaN | NaN | NaN | NaN |
4 | 5 | 180005 | 男 | 18国际金融 | 20945770 | NaN | NaN | NaN | NaN | NaN |
# 查看缺失值占比
data1_merge_data3.apply(lambda x : sum(x.isnull()) / len(x), axis=0)
序号_x 0.000000
校园卡号 0.000000
性别 0.000000
专业名称 0.000000
门禁卡号 0.000000
序号_y 0.157723
进出时间 0.157723
进出地点 0.157723
是否通过 0.157723
描述 0.157723
dtype: float64
# 删除
print('删除缺失值前: ',data1_merge_data3.shape)
data1_merge_data3 = data1_merge_data3.dropna(subset=['进出地点'], how='any')
print('删除缺失值后:', data1_merge_data3.shape)
删除缺失值前: (21709, 10)
删除缺失值后: (18285, 10)
# 展示数据
data1_merge_data3.head(3)
序号_x | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 序号_y | 进出时间 | 进出地点 | 是否通过 | 描述 | |
---|---|---|---|---|---|---|---|---|---|---|
40 | 41 | 180041 | 女 | 18国际金融 | 19748650 | 1346249.0 | 2019-04-02 09:46:00 | 青鸾苑[出门] | 1.0 | 允许通过 |
41 | 41 | 180041 | 女 | 18国际金融 | 19748650 | 1346258.0 | 2019-04-02 10:31:00 | 青鸾苑[进门] | 1.0 | 允许通过 |
42 | 41 | 180041 | 女 | 18国际金融 | 19748650 | 1346260.0 | 2019-04-02 00:00:00 | 青鸾苑[出门] | 1.0 | 允许通过 |
data1_merge_data3.to_csv('./task1_2_2.csv', index=False, encoding='utf-8-sig')
Part2 食堂就餐行为分析
任务2.1
绘制各食堂就餐人次的占比图,分析学生早中晚的就餐地点是否有差别,并在报告中进行描述。
data = pd.read_csv('./task1_2_1.csv')
data.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117331517.0 | 20181.0 | 2019-04-21 18:30:00 | 7.0 | 0.0 | 28.4 | 206.0 | 消费 | 41.0 | 249.0 | 第四食堂 |
1 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117341866.0 | 20181.0 | 2019-04-22 09:40:00 | 3.5 | 0.0 | 24.9 | 207.0 | 消费 | 19.0 | 236.0 | 第一食堂 |
2 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117154618.0 | 20181.0 | 2019-04-10 16:42:00 | 11.0 | 0.0 | 2.7 | 189.0 | 消费 | 82.0 | 18.0 | 第四食堂 |
# 导入绘图库
#设置字体
from pylab import mpl
import matplotlib.pyplot as plt
mpl.rcParams["font.sans-serif"] = ["SimHei"] # 显示中文
plt.rcParams['axes.unicode_minus'] = False # 显示负号
# 忽略警告
import warnings
warnings.filterwarnings('ignore')
# 将食堂数据按食堂分类,并汇总数据量
canteen1 = data['消费地点'].apply(str).str.contains('第一食堂').sum()
canteen2 = data['消费地点'].apply(str).str.contains('第二食堂').sum()
canteen3 = data['消费地点'].apply(str).str.contains('第三食堂').sum()
canteen4 = data['消费地点'].apply(str).str.contains('第四食堂').sum()
canteen5 = data['消费地点'].apply(str).str.contains('第五食堂').sum()
apply
:转换类型
contains
:字符串匹配
# 绘制图形
cantee_names = ['食堂1', '食堂2', '食堂3', '食堂4', '食堂5']
man_count = [canteen1, canteen2, canteen3, canteen4, canteen5]
# 插件画布
plt.figure(figsize=(10, 6))
# 绘制饼图
plt.pie(man_count, labels=cantee_names, autopct='%1.2f%%')
# 显示比例
plt.legend()
# 添加标题
plt.title('食堂就餐人数占比图')
# 保持图像
plt.axis('equal')
# 显示图像
plt.show()
任务2.2
通过食堂刷卡记录,分别绘制工作日和非工作日食堂就餐时间曲线图,分析食堂早中晚的就餐峰值,并在报告中进行描述。
data.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117331517.0 | 20181.0 | 2019-04-21 18:30:00 | 7.0 | 0.0 | 28.4 | 206.0 | 消费 | 41.0 | 249.0 | 第四食堂 |
1 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117341866.0 | 20181.0 | 2019-04-22 09:40:00 | 3.5 | 0.0 | 24.9 | 207.0 | 消费 | 19.0 | 236.0 | 第一食堂 |
2 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117154618.0 | 20181.0 | 2019-04-10 16:42:00 | 11.0 | 0.0 | 2.7 | 189.0 | 消费 | 82.0 | 18.0 | 第四食堂 |
# 观察数据类型
data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 241079 entries, 0 to 241078
Data columns (total 16 columns):# Column Non-Null Count Dtype
--- ------ -------------- ----- 0 序号 241079 non-null int64 1 校园卡号 241079 non-null int64 2 性别 241079 non-null object 3 专业名称 241079 non-null object 4 门禁卡号 241079 non-null int64 5 流水号 241079 non-null float646 校园卡编号 241079 non-null float647 消费时间 241079 non-null object 8 消费金额 241079 non-null float649 存储金额 241079 non-null float6410 余额 241079 non-null float6411 消费次数 241079 non-null float6412 消费类型 241079 non-null object 13 消费项目编码 241079 non-null float6414 操作编码 241079 non-null float6415 消费地点 241079 non-null object
dtypes: float64(8), int64(3), object(5)
memory usage: 29.4+ MB
# 日期时间转换
data['消费时间'] = pd.to_datetime(data['消费时间'], format='%Y-%m-%d %H:%M:%S')
data.dtypes
序号 int64
校园卡号 int64
性别 object
专业名称 object
门禁卡号 int64
流水号 float64
校园卡编号 float64
消费时间 datetime64[ns]
消费金额 float64
存储金额 float64
余额 float64
消费次数 float64
消费类型 object
消费项目编码 float64
操作编码 float64
消费地点 object
dtype: object
# 消费日和非消费日,故创建一个消费星期列
data['消费星期'] = data['消费时间'].dt.dayofweek + 1
data.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | 消费星期 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117331517.0 | 20181.0 | 2019-04-21 18:30:00 | 7.0 | 0.0 | 28.4 | 206.0 | 消费 | 41.0 | 249.0 | 第四食堂 | 7 |
1 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117341866.0 | 20181.0 | 2019-04-22 09:40:00 | 3.5 | 0.0 | 24.9 | 207.0 | 消费 | 19.0 | 236.0 | 第一食堂 | 1 |
2 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117154618.0 | 20181.0 | 2019-04-10 16:42:00 | 11.0 | 0.0 | 2.7 | 189.0 | 消费 | 82.0 | 18.0 | 第四食堂 | 3 |
# 周一到周五工作日,其他为非工作日,拆分数据
# 创建 bool 索引
work_day_query = data.loc[:, '消费星期'] <= 5
unwork_day_query = data.loc[:, '消费星期'] > 5
# 获取数据
work_day_data = data.loc[work_day_query, :]
unwork_day_data = data.loc[unwork_day_query, :]
work_day_data.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | 消费星期 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
1 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117341866.0 | 20181.0 | 2019-04-22 09:40:00 | 3.5 | 0.0 | 24.9 | 207.0 | 消费 | 19.0 | 236.0 | 第一食堂 | 1 |
2 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117154618.0 | 20181.0 | 2019-04-10 16:42:00 | 11.0 | 0.0 | 2.7 | 189.0 | 消费 | 82.0 | 18.0 | 第四食堂 | 3 |
3 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117200032.0 | 20181.0 | 2019-04-15 11:43:00 | 7.0 | 0.0 | 43.2 | 192.0 | 消费 | 62.0 | 2.0 | 第四食堂 | 1 |
# 计算消费工作日对于时间消费的总次数
work_day_times = work_day_data['消费时间'].dt.hour.value_counts().sort_index()
work_day_times = work_day_times.tolist()
# 时间为x,同一时间段出现的次数为y
x = []
for i in range(24):x.append('{:02d}:00'.format(i))# 绘图
plt.plot(x, work_day_times, label='工作日')
plt.xlabel('时间')
plt.ylabel('次数')
plt.xticks(rotation=60)
plt.legend()
plt.grid()
plt.show()
unwork_day_times = []
for i in range(24):hour_str = f"{i:02d}"count = unwork_day_data['消费时间'].apply(lambda x : x.strftime('%H')).str.contains(hour_str).sum()unwork_day_times.append(count if count > 0 else 0)
# 绘图
plt.plot(x, unwork_day_times, label='非工作日')
plt.xlabel('时间')
plt.ylabel('次数')
plt.xticks(rotation=60)
plt.legend()
plt.grid()
plt.show()
Part3 学生消费行为分析
任务3.1
根据学生的整体校园消费数据,计算本月人均刷卡频次和人均消费额,并选择3个专业,分析不同专业之间不同性别学生群体的消费特点。
任务分析:
- 任务:计算本月人均刷卡频次、人均消费额、分析不同专业不同性别学生消费特点;
- 要求:选择不同三个专业。
data = pd.read_csv('./task1_2_1.csv')
data.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
0 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117331517.0 | 20181.0 | 2019-04-21 18:30:00 | 7.0 | 0.0 | 28.4 | 206.0 | 消费 | 41.0 | 249.0 | 第四食堂 |
1 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117341866.0 | 20181.0 | 2019-04-22 09:40:00 | 3.5 | 0.0 | 24.9 | 207.0 | 消费 | 19.0 | 236.0 | 第一食堂 |
2 | 1 | 180001 | 男 | 18国际金融 | 19762330 | 117154618.0 | 20181.0 | 2019-04-10 16:42:00 | 11.0 | 0.0 | 2.7 | 189.0 | 消费 | 82.0 | 18.0 | 第四食堂 |
# 计算人均刷卡频次(总刷卡数量/学生人数)
cost_count = data['消费时间'].count()
student_count = data['校园卡号'].value_counts(dropna=False).count()
average_cost_count = int(round(cost_count / student_count))
print("人均刷卡频次: ", average_cost_count)
人均刷卡频次: 74
# 计算人均消费金额(总消费金额/学生人数)
cost_sum = data['消费金额'].sum()
average_cost_money = int(round(cost_sum / student_count))
print("人均消费额度: ", average_cost_money)
人均消费额度: 285
# 统计不同专业人数
data['专业名称'].value_counts()
专业名称
18连锁经营 12100
18机械制造 11181
18会计 10055
18宝玉石鉴定 8602
18金融管理 8494
18国际商务 8339
18模具设计 8331
18国贸实务 8201
18软件技术 8166
18商务英语 7452
18工程造价 7131
18旅游管理 6873
18皮具艺术 6792
18电子商务 6754
18审计 6564
18工业机器人 6463
18工业设计 6300
18建筑工程 6279
18电气自动化 6171
18建筑设计 6140
18计算机网络 6110
18工商企管 5763
18投资与理财 5544
18社会工作 5510
18汽车检测 5093
18市场营销 5092
18计算机应用 4546
18国际金融 4351
18艺术设计 4278
18嵌入式技术 4196
18商务日语 4154
18工业工程 3987
18酒店管理 3895
18物流管理 3666
18动漫设计 3520
18首饰设计 3411
18视觉传播 2941
18环境艺术 2853
18产品艺术 2550
18市政工程 1840
18机械制造(学徒) 1391
Name: count, dtype: int64
排名前三的是:18连锁经营、18机械制造、18会计
注意: 18机械制造是学徒,不算
# 选取消费最多的三个专业
subject1 = data['专业名称'].apply(str).str.contains('18连锁经营')
subject2 = data['专业名称'].apply(str).str.contains('18机械制造')
subject3 = data['专业名称'].apply(str).str.contains('18会计')
subject4 = data['专业名称'].apply(str).str.contains('18机械制造(学徒)')# 去除学徒的,这一步也是数据聚合
data_new = data[(subject1 | subject2 | subject3) ^ subject4]
data_new['专业名称'].value_counts()
专业名称
18连锁经营 12100
18机械制造 11181
18会计 10055
Name: count, dtype: int64
# 分别选取男生、女生数据
data_male = data_new[data_new['性别'] == '男']
data_female = data_new[data_new['性别'] == '女']
data_male.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3698 | 74 | 180074 | 男 | 18会计 | 19832682 | 117305353.0 | 201874.0 | 2019-04-21 16:10:00 | 0.0 | 100.0 | 115.9 | 734.0 | 存款 | 202.0 | 143.0 | 财务处 |
3699 | 74 | 180074 | 男 | 18会计 | 19832682 | 117330658.0 | 201874.0 | 2019-04-22 09:37:00 | 2.6 | 0.0 | 106.2 | 737.0 | 消费 | 114.0 | 8.0 | 第五食堂 |
3700 | 74 | 180074 | 男 | 18会计 | 19832682 | 117330659.0 | 201874.0 | 2019-04-22 09:38:00 | 2.1 | 0.0 | 104.1 | 738.0 | 消费 | 114.0 | 8.0 | 第五食堂 |
data_female.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
4134 | 81 | 180081 | 女 | 18会计 | 18929482 | 117308277.0 | 201881.0 | 2019-04-21 14:03:00 | 2.0 | 0.0 | 23.3 | 616.0 | 消费 | 196.0 | 133.0 | 好利来食品店 |
4135 | 81 | 180081 | 女 | 18会计 | 18929482 | 117308744.0 | 201881.0 | 2019-04-22 09:32:00 | 2.0 | 0.0 | 17.9 | 619.0 | 消费 | 196.0 | 133.0 | 好利来食品店 |
4136 | 81 | 180081 | 女 | 18会计 | 18929482 | 117320320.0 | 201881.0 | 2019-04-21 16:41:00 | 3.0 | 0.0 | 19.9 | 618.0 | 消费 | 111.0 | 204.0 | 第五食堂 |
消费特点分析:
- 特征选择:性别、总消费额、消费地点、总消费次数、消费时间
# 这一步:特征提取,计算不同学生的消费总额、消费次数
# 统计性别,并且从新设计索引
data_1 = data[['校园卡号', '性别']].drop_duplicates().reset_index(drop=True)
# 性别进行编码
data_1['性别'] = data['性别'].astype(str).replace(({'男': 1, '女': 0}))
# 按照校园卡号设置索引
data_1.set_index(['校园卡号'], inplace=True)
# 统计消费金额
# 只提取消费金额那一列
data_2 = data.groupby('校园卡号').sum()[['消费金额']]
data_2.columns = ['消费金额'] # 设置标题
# 统计消费次数
data_3 = data.groupby('校园卡号').count()[['消费时间']]
data_3.columns = ['总消费次数']
data_3
总消费次数 | |
---|---|
校园卡号 | |
180001 | 35 |
180002 | 47 |
180004 | 100 |
180005 | 36 |
180006 | 23 |
... | ... |
184335 | 4 |
184336 | 51 |
184337 | 73 |
184338 | 57 |
184339 | 86 |
3268 rows × 1 columns
# 数据聚合
data_123 = pd.concat([data_1,data_2,data_3], axis=1)
data_123.head(3)
性别 | 消费金额 | 总消费次数 | |
---|---|---|---|
校园卡号 | |||
180001 | 1 | 161.6 | 35 |
180002 | 1 | 126.8 | 47 |
180004 | 1 | 572.0 | 100 |
分析:采用聚类算法
from sklearn.cluster import KMeans # 导入K-Meansk = 3 # 分成三类:高、中、低消费
iteration = 500 # 单次最大迭代数
# 数据标准化
data_zs = 1.0 * (data_123 - data_123.mean()) / data_123.std()# 创建聚类模型
model = KMeans(n_clusters=k, max_iter=iteration, random_state=123)# 聚类
model.fit(data_zs)
# 统计各类别的数目,
r1 = pd.Series(model.labels_).value_counts() # 返回每一个聚类数量
# 聚类中心
r2 = pd.DataFrame(model.cluster_centers_) # 返回每一个类别值
r = pd.concat([r2, r1], axis=1)
r.columns = list(data_123.columns) + ['类别数目']
# 将聚类类别加入data_123
r = pd.concat([data_123, pd.Series(model.labels_, index=data_123.index)], axis=1)
r.columns = list(data_123.columns) + ['聚类类别']
r.tail()
性别 | 消费金额 | 总消费次数 | 聚类类别 | |
---|---|---|---|---|
校园卡号 | ||||
184335 | 0 | 41.0 | 4 | 2 |
184336 | 0 | 200.6 | 51 | 2 |
184337 | 0 | 377.5 | 73 | 0 |
184338 | 0 | 296.7 | 57 | 2 |
184339 | 0 | 223.5 | 86 | 2 |
任务3.3
通过对低消费学生群体的行为进行分析,探讨是否存在某些特征,能为学校助学金评定提供参考
# 统计消费金额最低500名的学生信息
# 统计
data_500 = data.groupby('校园卡号').sum()[['消费金额']]
# 排序
data_500.sort_values(by=['消费金额'], ascending=True, inplace=True)
# 选取
data_500 = data_500.head(500)
# 选取索引值
data_500_index = data_500.index.values # values:转化成array
# 过滤
data_500 = data[data['校园卡号'].isin(data_500_index)]
data_500.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
35 | 2 | 180002 | 男 | 18国际金融 | 20521594 | 117309755.0 | 20182.0 | 2019-04-22 12:01:00 | 0.0 | 100.0 | 103.5 | 261.0 | 存款 | 202.0 | 143.0 | 财务处 |
36 | 2 | 180002 | 男 | 18国际金融 | 20521594 | 117394486.0 | 20182.0 | 2019-04-23 07:55:00 | 2.0 | 0.0 | 101.5 | 262.0 | 消费 | 105.0 | 236.0 | 第一食堂 |
37 | 2 | 180002 | 男 | 18国际金融 | 20521594 | 117394487.0 | 20182.0 | 2019-04-23 07:56:00 | 2.0 | 0.0 | 99.5 | 263.0 | 消费 | 105.0 | 236.0 | 第一食堂 |
# 取出最低消费的500人,统计其最常去的消费地点
data_500['最常去的消费地点'] = data_500.groupby('校园卡号')['消费地点'].transform(lambda x : x.mode().iloc[0])
data_500.head(3)
序号 | 校园卡号 | 性别 | 专业名称 | 门禁卡号 | 流水号 | 校园卡编号 | 消费时间 | 消费金额 | 存储金额 | 余额 | 消费次数 | 消费类型 | 消费项目编码 | 操作编码 | 消费地点 | 最常去的消费地点 | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
35 | 2 | 180002 | 男 | 18国际金融 | 20521594 | 117309755.0 | 20182.0 | 2019-04-22 12:01:00 | 0.0 | 100.0 | 103.5 | 261.0 | 存款 | 202.0 | 143.0 | 财务处 | 第一食堂 |
36 | 2 | 180002 | 男 | 18国际金融 | 20521594 | 117394486.0 | 20182.0 | 2019-04-23 07:55:00 | 2.0 | 0.0 | 101.5 | 262.0 | 消费 | 105.0 | 236.0 | 第一食堂 | 第一食堂 |
37 | 2 | 180002 | 男 | 18国际金融 | 20521594 | 117394487.0 | 20182.0 | 2019-04-23 07:56:00 | 2.0 | 0.0 | 99.5 | 263.0 | 消费 | 105.0 | 236.0 | 第一食堂 | 第一食堂 |
# 统计去的各个食堂数目
data_max_place = data_500['最常去的消费地点'].value_counts(dropna=False)
data_max_place
最常去的消费地点
第二食堂 3225
第五食堂 2792
第三食堂 1075
第一食堂 1019
好利来食品店 538
第四食堂 536
红太阳超市 24
财务部 22
水电缴费处 10
教师食堂 7
第七教学楼 4
医务室 1
第一图书馆 1
Name: count, dtype: int64
# 画图显示
canteen_name = list(data_max_place.index)
man_count = list(data_max_place.values)
# 创建画布
plt.figure(figsize=(20, 10))
plt.pie(man_count, labels=canteen_name, autopct='%1.2f%%')
plt.legend()
# 添加标题
plt.title('最低消费学生常去地方')
plt.show()
# 统计余额最大值和最小值
data_500['余额'].min()
0.0
data_500['余额'].max()
326.4
data_500['消费金额'].min()
0.0
data_500['消费金额'].max()
128.25
data['消费金额'].max()
300.0