2023年MathorCup建模思路 - 復(fù)盤:校園消費(fèi)行為分析
2023年第十三屆MathorCup高校數(shù)學(xué)建模挑戰(zhàn)賽
資料思路分享Q群:714452621
校園一卡通是集身份認(rèn)證、金融消費(fèi)、數(shù)據(jù)共享等多項(xiàng)功能于一體的信息集成系統(tǒng)。在為師生提供優(yōu)質(zhì)、高效信息化服務(wù)的同時(shí),系統(tǒng)自身也積累了大量的歷史記錄,其中蘊(yùn)含著學(xué)生的消費(fèi)行為以及學(xué)校食堂等各部門的運(yùn)行狀況等信息。
很多高?;谛@一卡通系統(tǒng)進(jìn)行“智慧校園”的相關(guān)建設(shè),例如《揚(yáng)子晚報(bào)》2016年 1月 27日的報(bào)道:《南理工給貧困生“暖心飯卡補(bǔ)助”》。
不用申請,不用審核,飯卡上竟然能悄悄多出幾百元……記者昨天從南京理工大學(xué)獨(dú)家了解到,南理工教育基金會(huì)正式啟動(dòng)了“暖心飯卡”
項(xiàng)目,針對特困生的溫飽問題進(jìn)行“精準(zhǔn)援助”。
項(xiàng)目專門針對貧困本科生的“溫飽問題”進(jìn)行援助。在學(xué)校一卡通中心,教育基金會(huì)的工作人員找來了全校一萬六千余名在校本科生 9 月中旬到 11月中旬的刷卡記錄,對所有的記錄進(jìn)行了大數(shù)據(jù)分析。最終圈定了 500余名“準(zhǔn)援助對象”。
南理工教育基金會(huì)將拿出“種子基金”100萬元作為啟動(dòng)資金,根據(jù)每位貧困學(xué)生的不同情況確定具體的補(bǔ)助金額,然后將這些錢“悄無聲息”的打入學(xué)生的飯卡中,保證困難學(xué)生能夠吃飽飯。
——《揚(yáng)子晚報(bào)》2016年 1月 27日:南理工給貧困生“暖心飯卡補(bǔ)助”本賽題提供國內(nèi)某高校校園一卡通系統(tǒng)一個(gè)月的運(yùn)行數(shù)據(jù),希望參賽者使用
數(shù)據(jù)分析和建模的方法,挖掘數(shù)據(jù)中所蘊(yùn)含的信息,分析學(xué)生在校園內(nèi)的學(xué)習(xí)生活行為,為改進(jìn)學(xué)校服務(wù)并為相關(guān)部門的決策提供信息支持。
2. 分析目標(biāo)
1. 分析學(xué)生的消費(fèi)行為和食堂的運(yùn)營狀況,為食堂運(yùn)營提供建議。
2. 構(gòu)建學(xué)生消費(fèi)細(xì)分模型,為學(xué)校判定學(xué)生的經(jīng)濟(jì)狀況提供參考意見。
3. 數(shù)據(jù)說明
附件是某學(xué)校 2019年 4月 1 日至 4月 30日的一卡通數(shù)據(jù)
一共3個(gè)文件:data1.csv、data2.csv、data3.csv



4. 數(shù)據(jù)預(yù)處理
將附件中的 data1.csv、data2.csv、data3.csv三份文件加載到分析環(huán)境,對照附錄一,理解字段含義。探查數(shù)據(jù)質(zhì)量并進(jìn)行缺失值和異常值等方面的必要處理。將處理結(jié)果保存為“task1_1_X.csv”(如果包含多張數(shù)據(jù)表,X可從 1 開始往后編號),并在報(bào)告中描述處理過程。
import numpy as np
import pandas as pd
import os
os.chdir('/home/kesci/input/2019B1631')
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.head(3)

data1.columns = ['序號', '校園卡號', '性別', '專業(yè)名稱', '門禁卡號']
data1.dtypes

data1.to_csv('/home/kesci/work/output/2019B/task1_1_1.csv', index=False, encoding='gbk')
data2.head(3)

將 data1.csv中的學(xué)生個(gè)人信息與 data2.csv中的消費(fèi)記錄建立關(guān)聯(lián),處理結(jié)果保存為“task1_2_1.csv”;將 data1.csv 中的學(xué)生個(gè)人信息與data3.csv 中的門禁進(jìn)出記錄建立關(guān)聯(lián),處理結(jié)果保存為“task1_2_2.csv”。
data1 = pd.read_csv("/home/kesci/work/output/2019B/task1_1_1.csv", encoding="gbk")
data2 = pd.read_csv("/home/kesci/work/output/2019B/task1_1_2.csv", encoding="gbk")
data3 = pd.read_csv("/home/kesci/work/output/2019B/task1_1_3.csv", encoding="gbk")
data1.head(3)

5. 數(shù)據(jù)分析
5.1 食堂就餐行為分析
繪制各食堂就餐人次的占比餅圖,分析學(xué)生早中晚餐的就餐地點(diǎn)是否有顯著差別,并在報(bào)告中進(jìn)行描述。(提示:時(shí)間間隔非常接近的多次刷卡記錄可能為一次就餐行為)
data = pd.read_csv('/home/kesci/work/output/2019B/task1_2_1.csv', encoding='gbk')
data.head()

import matplotlib as mpl
import matplotlib.pyplot as plt
# notebook嵌入圖片
%matplotlib inline
# 提高分辨率
%config InlineBackend.figure_format='retina'
from matplotlib.font_manager import FontProperties
font = FontProperties(fname="/home/kesci/work/SimHei.ttf")
import warnings
warnings.filterwarnings('ignore')canteen1 = data['消費(fèi)地點(diǎn)'].apply(str).str.contains('第一食堂').sum()
canteen2 = data['消費(fèi)地點(diǎn)'].apply(str).str.contains('第二食堂').sum()
canteen3 = data['消費(fèi)地點(diǎn)'].apply(str).str.contains('第三食堂').sum()
canteen4 = data['消費(fèi)地點(diǎn)'].apply(str).str.contains('第四食堂').sum()
canteen5 = data['消費(fèi)地點(diǎn)'].apply(str).str.contains('第五食堂').sum()
# 繪制餅圖
canteen_name = ['食堂1', '食堂2', '食堂3', '食堂4', '食堂5']
man_count = [canteen1,canteen2,canteen3,canteen4,canteen5]
# 創(chuàng)建畫布
plt.figure(figsize=(10, 6), dpi=50)
# 繪制餅圖
plt.pie(man_count, labels=canteen_name, autopct='%1.2f%%', shadow=False, startangle=90, textprops={'fontproperties':font})
# 顯示圖例
plt.legend(prop=font)
# 添加標(biāo)題
plt.title("食堂就餐人次占比餅圖", fontproperties=font)
# 餅圖保持圓形
plt.axis('equal')
# 顯示圖像
plt.show()

通過食堂刷卡記錄,分別繪制工作日和非工作日食堂就餐時(shí)間曲線圖,分析食堂早中晚餐的就餐峰值,并在報(bào)告中進(jìn)行描述。

# 對data中消費(fèi)時(shí)間數(shù)據(jù)進(jìn)行時(shí)間格式轉(zhuǎn)換,轉(zhuǎn)換后可作運(yùn)算,coerce將無效解析設(shè)置為NaT
data.loc[:,'消費(fèi)時(shí)間'] = pd.to_datetime(data.loc[:,'消費(fèi)時(shí)間'],format='%Y-%m-%d %H:%M',errors='coerce')
data.dtypes
# 創(chuàng)建一個(gè)消費(fèi)星期列,根據(jù)消費(fèi)時(shí)間計(jì)算出消費(fèi)時(shí)間是星期幾,Monday=1, Sunday=7
data['消費(fèi)星期'] = data['消費(fèi)時(shí)間'].dt.dayofweek + 1
data.head(3)
# 以周一至周五作為工作日,周六日作為非工作日,拆分為兩組數(shù)據(jù)
work_day_query = data.loc[:,'消費(fèi)星期'] <= 5
unwork_day_query = data.loc[:,'消費(fèi)星期'] > 5
work_day_data = data.loc[work_day_query,:]
unwork_day_data = data.loc[unwork_day_query,:]
# 計(jì)算工作日消費(fèi)時(shí)間對應(yīng)的各時(shí)間的消費(fèi)次數(shù)
work_day_times = []
for i in range(24):
? ?work_day_times.append(work_day_data['消費(fèi)時(shí)間'].apply(str).str.contains(' {:02d}:'.format(i)).sum())
? ?# 以時(shí)間段作為x軸,同一時(shí)間段出現(xiàn)的次數(shù)和作為y軸,作曲線圖
x = []
for i in range(24):
? ?x.append('{:02d}:00'.format(i))
# 繪圖
plt.plot(x, work_day_times, label='工作日')
# x,y軸標(biāo)簽
plt.xlabel('時(shí)間', fontproperties=font);
plt.ylabel('次數(shù)', fontproperties=font)
# 標(biāo)題
plt.title('工作日消費(fèi)曲線圖', fontproperties=font)
# x軸傾斜60度
plt.xticks(rotation=60)
# 顯示label
plt.legend(prop=font)
# 加網(wǎng)格
plt.grid()

# 計(jì)算飛工作日消費(fèi)時(shí)間對應(yīng)的各時(shí)間的消費(fèi)次數(shù)
unwork_day_times = []
for i in range(24):
? ?unwork_day_times.append(unwork_day_data['消費(fèi)時(shí)間'].apply(str).str.contains(' {:02d}:'.format(i)).sum())
? ?# 以時(shí)間段作為x軸,同一時(shí)間段出現(xiàn)的次數(shù)和作為y軸,作曲線圖
x = []
for i in range(24):
? ?x.append('{:02d}:00'.format(i))
plt.plot(x, unwork_day_times, label='非工作日')
plt.xlabel('時(shí)間', fontproperties=font);
plt.ylabel('次數(shù)', fontproperties=font)
plt.title('非工作日消費(fèi)曲線圖', fontproperties=font)
plt.xticks(rotation=60)
plt.legend(prop=font)
plt.grid()

根據(jù)上述分析的結(jié)果,很容易為食堂的運(yùn)營提供建議,比如錯(cuò)開高峰等等。
5.2 學(xué)生消費(fèi)行為分析
根據(jù)學(xué)生的整體校園消費(fèi)數(shù)據(jù),計(jì)算本月人均刷卡頻次和人均消費(fèi)額,并選擇 3個(gè)專業(yè),分析不同專業(yè)間不同性別學(xué)生群體的消費(fèi)特點(diǎn)。
data = pd.read_csv('/home/kesci/work/output/2019B/task1_2_1.csv', encoding='gbk')
data.head()

# 計(jì)算人均刷卡頻次(總刷卡次數(shù)/學(xué)生總?cè)藬?shù))
cost_count = data['消費(fèi)時(shí)間'].count()
student_count = data['校園卡號'].value_counts(dropna=False).count()
average_cost_count = int(round(cost_count / student_count))
average_cost_count
# 計(jì)算人均消費(fèi)額(總消費(fèi)金額/學(xué)生總?cè)藬?shù))
cost_sum = data['消費(fèi)金額'].sum()
average_cost_money = int(round(cost_sum / student_count))
average_cost_money
# 選擇消費(fèi)次數(shù)最多的3個(gè)專業(yè)進(jìn)行分析
data['專業(yè)名稱'].value_counts(dropna=False)

# 消費(fèi)次數(shù)最多的3個(gè)專業(yè)為 連鎖經(jīng)營、機(jī)械制造、會(huì)計(jì)
major1 = data['專業(yè)名稱'].apply(str).str.contains('18連鎖經(jīng)營')
major2 = data['專業(yè)名稱'].apply(str).str.contains('18機(jī)械制造')
major3 = data['專業(yè)名稱'].apply(str).str.contains('18會(huì)計(jì)')
major4 = data['專業(yè)名稱'].apply(str).str.contains('18機(jī)械制造(學(xué)徒)')
data_new = data[(major1 | major2 | major3) ^ major4]
data_new['專業(yè)名稱'].value_counts(dropna=False)
分析 每個(gè)專業(yè),不同性別 的學(xué)生消費(fèi)特點(diǎn)
data_male = data_new[data_new['性別'] == '男']
data_female = data_new[data_new['性別'] == '女']
data_female.head()

根據(jù)學(xué)生的整體校園消費(fèi)行為,選擇合適的特征,構(gòu)建聚類模型,分析每一類學(xué)生群體的消費(fèi)特點(diǎn)。
data['專業(yè)名稱'].value_counts(dropna=False).count()
# 選擇特征:性別、總消費(fèi)金額、總消費(fèi)次數(shù)
data_1 = data[['校園卡號','性別']].drop_duplicates().reset_index(drop=True)
data_1['性別'] = data_1['性別'].astype(str).replace(({'男': 1, '女': 0}))
data_1.set_index(['校園卡號'], inplace=True)
data_2 = data.groupby('校園卡號').sum()[['消費(fèi)金額']]
data_2.columns = ['總消費(fèi)金額']
data_3 = data.groupby('校園卡號').count()[['消費(fèi)時(shí)間']]
data_3.columns = ['總消費(fèi)次數(shù)']
data_123 = ?pd.concat([data_1, data_2, data_3], axis=1)#.reset_index(drop=True)
data_123.head()
# 構(gòu)建聚類模型
from sklearn.cluster import KMeans
# k為聚類類別,iteration為聚類最大循環(huán)次數(shù),data_zs為標(biāo)準(zhǔn)化后的數(shù)據(jù)
k = 3 ? ?# 分成幾類可以在此處調(diào)整
iteration = 500
data_zs = 1.0 * (data_123 - data_123.mean()) / data_123.std()
# n_jobs為并發(fā)數(shù)
model = KMeans(n_clusters=k, n_jobs=4, max_iter=iteration, random_state=1234)
model.fit(data_zs)
# r1統(tǒng)計(jì)各個(gè)類別的數(shù)目,r2找出聚類中心
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) + ['類別數(shù)目']
# 選出消費(fèi)總額最低的500名學(xué)生的消費(fèi)信息
data_500 = data.groupby('校園卡號').sum()[['消費(fèi)金額']]
data_500.sort_values(by=['消費(fèi)金額'],ascending=True,inplace=True,na_position='first')
data_500 = data_500.head(500)
data_500_index = data_500.index.values
data_500 = data[data['校園卡號'].isin(data_500_index)]
data_500.head(10)

# 繪制餅圖
canteen_name = list(data_max_place.index)
man_count = list(data_max_place.values)
# 創(chuàng)建畫布
plt.figure(figsize=(10, 6), dpi=50)
# 繪制餅圖
plt.pie(man_count, labels=canteen_name, autopct='%1.2f%%', shadow=False, startangle=90, textprops={'fontproperties':font})
# 顯示圖例
plt.legend(prop=font)
# 添加標(biāo)題
plt.title("低消費(fèi)學(xué)生常消費(fèi)地點(diǎn)占比餅圖", fontproperties=font)
# 餅圖保持圓形
plt.axis('equal')
# 顯示圖像
plt.show()

??
2023年第十三屆MathorCup高校數(shù)學(xué)建模挑戰(zhàn)賽
資料思路分享Q群:714452621