2019年テディカップ データ分析スキルコンテスト 問題B - キャンパス内の学生の消費行動の分析

    

 タスク 1.1

1. データインポート

import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
import random
plt.rcParams['font.family'] = 'SimHei'      # 正常显示中文
plt.rcParams['axes.unicode_minus'] = False


data1 = pd.read_csv('data1.csv',sep=',',encoding='gbk')
data1.columns =['序号','校园卡号','性别','专业名称','门禁卡号']

data2 = pd.read_csv('data2.csv',sep=',',encoding='gbk')
data2.columns=['流水号','校园卡号','学号','消费时间','消费金额','充值金额','余额',
               '消费次数','消费类型','消费项目编码','消费项目序号','消费操作编码','操作编码','消费地点']

data3 = pd.read_csv(r'data3.csv',sep=',',encoding='gbk')
data3.columns =['序号','门禁卡号','出入日期','出入地点','进出成功编号','通过权限']

データ1:

データ2:

データ3:

 2. 欠損値分析:

 data2 データの消費品目のシリアル番号と消費品目コードが90% 以上欠落しており、実用的な分析の意味がないため、これらを削除します。

# 删除缺失值过多的列
data2 = data2.drop(['消费项目序号','消费操作编码'],axis = 1)

3. 外れ値分析:

Data1 箱ひげ図分析:

def boxplot(data):
    fig = plt.figure(figsize = (20,20))
    for i,col in enumerate(data.columns):
        plt.subplot(4,3,i+1)
        data[[col]].boxplot()
    plt.show()
    
boxplot(data1[['校园卡号','门禁卡号']])

 キャンパス カード番号の異常なデータを調べます。

 上記のことから、2 つの異常なキャンパス カード番号はおそらく 18 で始まり 16 に変更されたことがわかります。変更するだけです。

data1['校园卡号'].replace({164340:184340,164341:184341},inplace=True)

 アクセスコントロールカード番号の異常データの場合:

data2 箱ひげ図分析:

def get_colors(color_style):
    cnames = sns.xkcd_rgb
    if color_style =='light':
        colors = list(filter(lambda x:x[:5]=='light',cnames.keys()))
    elif color_style =='dark':
        colors = list(filter(lambda x:x[:4]=='dark',cnames.keys()))
    elif color_style =='all':
        colors = cnames.keys()
    colors = list(map(lambda x:cnames[x], colors))
    return colors

# 封装箱线图
def boxplot(data, rows = 3, cols = 4, figsize = (13, 8), vars  =None, hue = None, width = 0.25,
            color_style ='light',subplots_adjust = (0.2, 0.2)):
    
    fig = plt.figure(figsize = figsize)
    hue = data[hue] if isinstance(hue,str) and hue in data.columns else hue
    data = data if not vars else data[vars]
    
    colors = get_colors(color_style)
    ax_num = 1
    for col in data.columns:
        if isinstance(data[col].values[0],(np.int64,np.int32,np.int16,np.int8,np.float16,np.float32,np.float64)):
            plt.subplot(rows, cols, ax_num)
            sns.boxplot(x = hue,y = data[col].values,color=random.sample(colors,1)[0],width= width)
            plt.xlabel(col)
#             data[col].plot(kind = 'box',color=random.sample(colors,1)[0])
            ax_num+=1
    
    plt.subplots_adjust(hspace = subplots_adjust[0],wspace=subplots_adjust[1])
    plt.show()

boxplot(data2)

現実世界のシナリオと組み合わせた data2 データの外れ値を削除するかどうかを主観的に決定します。 

消費時間の特性を分析します。

# 将消费时间特征转换为datetime类型数据
time = pd.to_datetime(data2['消费时间']).dt.time
# 对消费时间点进行统计并按照时间排序后进行可视化分析
time.value_counts().sort_index().plot()
plt.title('消费记录统计')
plt.show()

 上記からも分かるように、0:00のデータ消費量が最も多く、明らかに表示状況と合っていませんが、システム上の都合等によりデフォルトの0:00となっている可能性が推測されます。時刻入力時のエラー。

ポイント0のデータ量は7,000件以上あり、これらのデータを元に今後の分析にまだ価値があるため、当面は削除等の処理は行いません。

data3 箱ひげ図分析:

time = pd.to_datetime(data3['出入日期']).dt.time
time.value_counts().sort_index().plot()
plt.title('门禁出入统计')
plt.show()

 

 分析は data2 と一致しているため、現時点では処理されません。

セーブデータ:

data1.to_csv('task1_1_1.csv')
data2.to_csv('task1_1_2.csv')
data3.to_csv('task1_1_3.csv')

 タスク 1.2

1. リンクテーブル

キャンパスカード番号に応じてdata1とdata2を紐づけ、記録したデータを取り出して利用します。

data_2_1 = pd.merge(data1,data2,left_on='校园卡号')
data_2_1 = data_2_1[data_2_1['消费类型']=='消费']

data_2_2 = pd.merge(data1,data3,on = '门禁卡号')

キャンパス カード番号に基づいて学生の数を単純に分析します。

a = data_2_1.校园卡号.unique().size
b = data2.校园卡号.unique().size
sns.set_style('whitegrid',rc = {'font.family': 'SimHei'})
AxesSubplot = sns.barplot(x = ['总校园卡号','为消费类型的校园卡号'],y = [b,a])
plt.bar_label(AxesSubplot.containers[0])

 一定期間提供された実際の消費実績データでは、消費実績のある学生が3,200人以上いることがわかります。

データ_2_1:

データ_2_2: 

 タスク 2.1

各食堂で食事をする人数の割合を円グラフに描き、朝食、昼食、夕食を食べる場所に大きな違いがあるかどうかを分析し、レポートに記載します。(ヒント: 非常に近い間隔での複数のクレジット カードのスワイプ記録は、食事行動である可能性があります)

朝食、昼食、夕食を定義します

import datetime
from datetime import time

# 取出食堂的消费记录数据
data_shitang = data2[(data2['消费地点'].map(lambda x:'食堂' in x)) & (data2['消费类型'] =='消费')]
data_shitang['消费时间'] = pd.to_datetime(data_shitang.消费时间)

def eating_time(x):
    y = []
    for i in x:
        if time(5,0)<=i.time()<time(10,30):
            y.append('早餐')
        elif time(10,35)<=i.time()<time(16,30):
            y.append('午餐')
        elif time(16,30)<=i.time()<time(23,30):
            y.append('晚餐')
        else :
            y.append('不明确')
    return y

data_shitang['就餐类型'] = eating_time(data_shitang['消费时间'])

 各食堂での朝食、昼食、夕食のカードのスワイプ数を統計的に分析します。

fig, axes = plt.subplots(2, 3, figsize = (12, 7))
   
ax = axes.ravel()
labels = data_shitang['消费地点'].unique()
colors = list(map(lambda x:sns.xkcd_rgb[x], sns.xkcd_rgb.keys()))
colors = np.random.choice(colors,5)

ax_num = 0
for label in labels:
    data_ = data_shitang[data_shitang['消费地点']==label]  # 取出一个类别的数据
    # 对该类别数据每个特征进行统计

    d = data_['就餐类型'].value_counts()

    ax[ax_num].pie(labels = d.index, x = d.values, autopct='%.1f%%',colors = colors)
#        ax.pie(d.values, labels = d.values)
    ax[ax_num].set_title(label, fontsize = 13)
    ax_num+=1

plt.subplots_adjust(0.2,0.2)

  

 上記から、カードをスワイプすることで各食堂の消費状況や食事の種類、例えば教員食堂は昼食のみ、学生は主に第3食堂と第4食堂で昼食と夕食を消費するなどの分析が可能となります。

朝、昼、晩の各カフェテリアの食事行動を分析し、円グラフを描画します

 この課題の難しさは、質問の意味と現実を合わせて、主に食事数の決定にあります(質問の意味からすると、カードをスワイプした回数をそのまま食事数とはみなせません)。では、同じ食堂での 30 分以内の複数のカードのスワイプを 1 つの消費行動、つまり 1 回の食事の回数として定義します。異なる食堂で 30 分以内に複数のクレジット カードのスワイプを行っても、複数の食事としてカウントされます

# 使30分钟内的多次刷卡为一次刷卡记录
def time_filter(x):
    import datetime
    # 初始化消费次数为刷卡次数
    consums = len(x)
    # 对消费时间进行降序
    x = x.sort_values(ascending= False)
    # 定义变量使得能跳出datetime1已经计算过的在十分钟内的datetime2
    position = 0
    for num,datetime1 in enumerate(x):
        if position != 0:
            position -= 1 
            continue
        for datetime2 in x[num+1:]: 
            # 当时间小于30分钟时,consums消费次数-1
            if datetime1-datetime2<datetime.timedelta(seconds = 1800):
                consums -= 1
                position +=1
            else:
                break
    # 返回总消费次数      
    return consums 
 
# 获取每个食堂中每个客户的消费次数
d = data_shitang.groupby(['消费地点','校园卡号'],as_index =False)['消费时间'].agg(time_filter)
print(d)
# 统计每个食堂的消费次数
xiaofei_counts=d.groupby('消费地点')['消费时间'].sum()
xiaofei_counts.name = '消费次数'
print(xiaofei_counts)

plt.pie(labels = xiaofei_counts.index,x = xiaofei_counts, autopct='%.1f%%')
plt.title('各食堂总就餐人次占比饼图')
plt.show()

 

data_shitang_zaocan = data_shitang[data_shitang['就餐类型'] =='早餐']
data_shitang_wucan = data_shitang[data_shitang['就餐类型'] =='午餐']
data_shitang_wancan = data_shitang[data_shitang['就餐类型'] =='晚餐']
data_shitang_ = [data_shitang_zaocan, data_shitang_wucan, data_shitang_wancan]
data_leixing = ['早餐', '午餐', '晚餐']
fig,axes = plt.subplots(1,3, figsize = (14,6))
counts = [] # 存储早午晚餐统计数据
for d, title, ax in zip(data_shitang_, data_leixing, axes):
    d = d.groupby(['消费地点','校园卡号'],as_index =False)['消费时间'].agg(time_filter)
    xiaofei_counts=d.groupby('消费地点')['消费时间'].sum()
    xiaofei_counts.name = '消费次数'
    counts.append(xiaofei_counts)
    ax.pie(labels = xiaofei_counts.index,x = xiaofei_counts, autopct='%.1f%%')
    ax.set_title(f'{title}各食堂就餐人次占比饼图')
plt.show()

pyecharts を使用して円グラフを描画します

from pyecharts.charts import Pie
from pyecharts import options as opts 
def pie_(xiaofei_counts, label):
    pie = Pie()
    pie.add('就餐次数统计',[list(z) for z in zip(xiaofei_counts.index,xiaofei_counts)],radius = ['50%','70%'],
            rosetype = 'are',center=["50%", "53%"])
    pie.set_global_opts(title_opts = opts.TitleOpts(title=f'{label}行为分析饼图'),
                       legend_opts=opts.LegendOpts(pos_bottom = 0))
    # formatter中 a表示data_pair,b表示类别名,c表示类别数量,d表示百分数
    pie.set_series_opts(label_opts=opts.LabelOpts(
            position="outside",
            formatter="{a|{a}}{abg|}\n{hr|}\n {b|{b}: }{c}  {per|{d}%}  ",
            background_color="#eee",
            border_color="#aaa",
            border_width=1,
            border_radius=4,
            rich={
                "a": {"color": "#999", "lineHeight": 22, "align": "center"},
                "abg": {
                    "backgroundColor": "#e3e3e3",
                    "width": "100%",
                    "align": "right",
                    "height": 18,
                    "borderRadius": [4, 4, 0, 0],
                },
                "hr": {
                    "borderColor": "#aaa",
                    "width": "100%",
                    "borderWidth": 0.3,
                    "height": 0,
                },
                "b": {"fontSize": 14, "lineHeight": 33},
                "per": {
                    "color": "#eee",
                    "backgroundColor": "#334455",
                    "padding": [2, 4],
                    "borderRadius": 2,
                },
            },
        ),legend_opts =opts.LegendOpts(type_ = 'scroll',
                                                      
                                      orient = 'horizontal',align ='left',
                                      item_gap = 10,item_width = 25,item_height = 15,
                                      inactive_color = 'break'))

    pie.set_colors(['red',"orange", "yellow", "Cyan", "purple" ,"green","blue","#61e160","#d0fe1d"]) 
    return pie.render_notebook()

 

 大まかな分析:

タスク 2.2食堂でのカードのスワイプ記録を通じて、営業日と非営業日の食堂の食事時間曲線を描き、食堂での朝食、昼食、夕食の食事のピークを分析し、レポートに記載します。

統計:

# 获取小时数据
data_shitang['就餐时间'] = data_shitang['消费时间'].apply(lambda x:x.hour)
# 获取是否工作日
from chinese_calendar import is_workday,is_holiday
data_shitang['是否工作日'] = data_shitang['消费时间'].apply(lambda x: '工作日' if is_workday(x) else '非工作日')

# 获取工作日与非工作日的每个时间刷卡次数统计
data_isor_workday = data_shitang.groupby(['就餐时间','是否工作日']).size().unstack()
print(data_isor_workday)

# 工作日除以21天,非工作日除以9,得到日均刷卡次数
data_isor_workday = data_isor_workday/np.array([21,9])  
# 缺失值填0处理(有的时段无刷卡次数,如凌晨)     
data_isor_workday = data_isor_workday.fillna(0).astype(np.int)

視覚化:

plt.plot(data_isor_workday.index,data_isor_workday['工作日'], label = '工作日')
plt.plot(data_isor_workday.index,data_isor_workday['非工作日'], label = '非工作日')
plt.xlabel('时间')
plt.ylabel('日均刷卡次数')
plt.xticks(range(24))
plt.legend()
plt.show()

 pyecharst の視覚化:

import pyecharts
from pyecharts.charts import Line
from pyecharts import options as opts
from pyecharts.globals import ThemeType
from pyecharts import *


# 常用全局参数配置封装
def global_opts(line,x_name,y_name,title,bottom = None,left = None,split_line = False):
         line.set_global_opts(title_opts=opts.TitleOpts(title = title),
                             xaxis_opts=opts.AxisOpts(name= x_name,type_='category', name_location='center',name_gap=25,max_interval =0),
                             yaxis_opts=opts.AxisOpts(name= y_name,type_='value', name_location='end',name_gap=15,
                                                      splitline_opts=opts.SplitLineOpts(is_show=split_line,
                                                                                        linestyle_opts=opts.LineStyleOpts(opacity=1)),),
                              legend_opts =opts.LegendOpts(type_ = 'scroll',
                                                      pos_bottom=bottom, pos_left = left,
                                                      orient = 'horizontal',align ='left',
                                                      item_gap = 10,item_width = 25,item_height = 15,
                                                      inactive_color = 'break'),
                             tooltip_opts=opts.TooltipOpts(trigger="axis", axis_pointer_type="cross"),
                                                     )


def mul_line_plot(data_x,data_y,x_name,y_name,title,):

    line =Line(init_opts=opts.InitOpts(theme=ThemeType.DARK,bg_color = '',width='900px',height = '550px'))
    line.add_xaxis(data_x)
    for i in data_y.columns:
        line.add_yaxis(series_name = i,y_axis =data_y.loc[:,i],is_smooth =True,symbol_size = 6,
                        linestyle_opts=opts.LineStyleOpts( width=2, type_="solid"),
                        label_opts = opts.LabelOpts(is_show=True,position = 'top',font_size =12,
                                               font_style = 'italic',font_family= 'serif',))

    global_opts(line,x_name,y_name,title,bottom = 0,left = 20)

    return line.render_notebook()

mul_line_plot(data_x=(data_isor_workday).index.tolist(),data_y=(data_isor_workday),
              x_name ='时间',y_name='日均刷卡次数',title ='就餐时间曲线图')

 タスク 3.1学生のキャンパス全体の消費データに基づいて、今月の 1 人当たりのカード スワイプ頻度と 1 人当たりの消費額を計算し、3 つの専攻を選択して、異なる専攻の異なる性別の学生の消費特性を分析します。

d = data_2_1.groupby('校园卡号').agg({'消费次数':np.size,'消费金额':np.sum})[['消费金额','消费次数']]
# 封装箱线图
boxplot(data = d)

# 依据箱线图去除异常数据
d = d[ (d['消费金额'] < 800) & (d['消费次数'] < 180)] 
# 本月人均刷卡次数约72次 、人均消费总额288
print(d.mean())

消費者行動分析の専攻を 3 つ選択してください。 

さまざまな専攻およびさまざまな性別の一人当たりクレジット カード金額の比較表: 

data_3_zhuanye = data_2_1.query("专业名称 in ['18产品艺术','18会计','18动漫设计']")
a = data_3_zhuanye.groupby(['专业名称','性别'])['消费金额'].mean().unstack()
a = np.round(a,2) # 小数点两位且四舍五入

with sns.color_palette('rainbow_r'):
    bar = a.plot.bar()
    plt.xticks(rotation =0)
    plt.title('平均每次刷卡金额')
    for i in bar.containers:
        plt.bar_label(i)

 パイチャートを使用します。

bar = Bar()
bar.add_xaxis(a.index.tolist())

bar.add_yaxis('女',a.iloc[:,0].tolist(),itemstyle_opts=opts.ItemStyleOpts(color='red'))
bar.add_yaxis('男',a.iloc[:,1].tolist(),itemstyle_opts=opts.ItemStyleOpts(color='blue'))

global_opts(line = bar,title = '不同专业不同性别学生群体的关系',x_name = '专业',y_name = '平均刷卡金额/元')
bar.render_notebook()

 

さまざまな職業や性別の飲食店の円グラフ:

with sns.color_palette('rainbow'):
    # 封装函数,源程序在作者博客seaborn封装中可以找到
    count_pieplot(data_3_zhuanye,3,2,vars = ['消费地点','专业名称','性别'],hue = '专业名称',qita_percentage_max=  0.02,figsize=(6,11))

パイチャート:

d_ = pd.pivot_table(data =data_3_zhuanye ,index =['消费地点'],columns = '专业名称',aggfunc='size',).fillna(0)

pie_(d_['18产品艺术'].sort_values(ascending=False)[:8],'18产品艺术消费地点')
# pie_(d_['18会计'].sort_values(ascending=False)[:8],'18会计消费地点')
# pie_(d_['18动漫设计'].sort_values(ascending=False)[:8],'18动漫设计消费地点')

 

 

 

  

with sns.color_palette('rainbow'):
    # 作者封装函数,需要源程序可在作者博客seaborn封装中寻找
    count_pieplot(data_3_zhuanye,1,2,vars = ['消费地点'],hue = '性别',qita_percentage_max=  0.02,figsize=(10,4))

  さまざまな専攻の男子生徒の消費場所の円グラフ:

with sns.color_palette('rainbow'):
    count_pieplot(data_3_zhuanye.query("性别 == '男'"),1,3,vars = ['消费地点','专业名称'],hue = '专业名称',qita_percentage_max=  0.02,figsize=(16,4))

 

 さまざまな専攻の女子生徒の消費場所の円グラフ:

パイチャート:

d_ = pd.pivot_table(data =data_3_zhuanye ,index =['消费地点'],columns = '性别',aggfunc='size',).fillna(0)

pie_(d_['男'].sort_values(ascending=False)[:10],'男生消费地点')
# pie_(d_['女'].sort_values(ascending=False)[:10],'女生消费地点')

 

タスク 3.2学生の全体的なキャンパス消費行動に従って、適切な特徴を選択し、クラスタリング モデルを構築し、各タイプの学生グループの消費特性を分析します。

バックグラウンド分析と組み合わせて、各スワイプカードの平均消費量、総消費時間、総消費量の 3 つの特徴を取り出してクラスタリングします。

import sklearn
from sklearn import  cluster
from sklearn.preprocessing import StandardScaler

# 取出日常消费类型数据
data_2_1_1 = data_2_1.query("消费地点 in ['第四食堂','第一食堂','第二食堂', '红太阳超市','第五食堂','第三食堂', '好利来食品店']")


# 取出每次刷卡平均消费金额、总消费次数、消费总金额三个特征进行聚类
data = data_2_1_1.groupby(['校园卡号'],as_index=False)['消费金额'].mean()
data['本月内消费累计次数'] = data_2_1_1.groupby('校园卡号')['消费次数'].size().values
data['消费总金额'] = data_2_1_1.groupby('校园卡号')['消费金额'].sum().values
data = data.set_index('校园卡号')
data.columns = ['平均每次刷卡消费金额','本月内累计消费次数','消费总金额']
print(data)

Kmeans クラスタリング:

# Kmeans聚类模型,七个聚类簇
model = cluster.KMeans(n_clusters=7)
# 标准化模型
scaler = StandardScaler()
# 标准化
data_ = scaler.fit_transform(data.iloc[:,:])
# 模型训练
model.fit(data_)

# 对数据进行聚类得到标签
labels = model.predict(data_)

# 将标签加入到data数据中
data['labels'] = labels

 視覚化:

2D 散布図

sns.set(font='SimHei')
sns.scatterplot(data =data , x = '本月内累计消费次数',y= '平均每次刷卡消费金额',hue = 'labels',palette = 'rainbow')
plt.title('七个消费群体散点图')
plt.show()

3D 散布図:

colors = ['#a88f59', '#da467d', '#fdb915', '#69d84f', '#380282','r','b']
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(15,8))
ax = fig.add_subplot(121, projection='3d')

for i in data['labels'].unique():
    d = data[data['labels']==i]
    ax.scatter(d['本月内累计消费次数'],d['平均每次刷卡消费金额'],d['消费总金额'],c=colors[i],label =i)
    ax.set_xlabel('本月内累计消费次数')
    ax.set_ylabel('平均每次刷卡消费金额')
    ax.set_zlabel('消费总金额')
plt.title('高钾:层次聚类结果图',fontsize = 15)
plt.legend()
plt.show()

タスク 3.3 では、学校補助金の評価に参考となる特定の特徴があるかどうかを調査するために、低消費生活の学生グループの行動を分析します。

低支出グループの特徴を分析および調査して、奨学金の評価に関する提案を提供します。

        タスク 3.2 のクラスター図に基づいて、実際の状況と組み合わせると、低消費グループは、消費頻度が低すぎるため、中程度の消費時間、低い平均消費量、および総消費量が低いという特徴を持つはずです。学生食堂での消費はめったになく、ほとんどが学食で行われる可能性があるが、学外の飲食店での消費は貧しい学生と判断できず、たとえ平均消費量が低くても消費時間が長いと総消費量が高くなるため、この分析に基づいて、グループ 1 は消費量が少ない貧しい学生の特徴を満たしていると判断します。また、グループ 2 の消費時間は 100 前後に分布しており、通常の消費時間と一致しています。したがって、学校が貧しい生徒のための補助金政策を実施する必要がある場合は、グループ 1 の生徒の左下隅から選択できます (左下隅は、より低いことを表します) 総消費量)。

その他の自律分析の視覚化:

 

ブログ内の 3 つのテディカップ分析コンテストの完全なプロジェクト情報は WeChat に追加できます: gjwtxp (20 元)

おすすめ

転載: blog.csdn.net/weixin_46707493/article/details/127146450