Python数据分析实战 —— 天猫订单数据分析

文章目录

  • 项目介绍
  • 数据介绍
  • 导入部分库
  • 数据预处理
  • 数据格式整理
  • 异常值分析
  • 数据分析
  • 描述性统计
  • 周趋势、日趋势分析
  • 产品价格分析
  • 地区分析
  • 转化率分析
  • 总结
  • 项目介绍

    本项目将对2020年2月份的真实天猫订单成交数据(共28010条记录)进行数据清洗、数据可视化、数据分析,阐述销售现状、挖掘潜在规律、发现存在问题、提出可行性建议。
    数据下载:https://tianchi.aliyun.com/competition/entrance/1/information

    下面是这个项目的分析导图。

    分析导图

    数据介绍

    包含了天猫2020年2月份的共28010条订单数据,有以下7个字段:

    订单编号:共28010条
    总金额:该笔订单的总金额
    买家实际支付金额:实际成交金额。分为已付款和未付款两种情况:
    已付款:买家实际支付金额 = 总金额 – 退款金额
    未付款:买家实际支付金额 = 0
    收货地址:维度为省份,共包含31个省市
    订单创建时间:2020年2月1日 至 2020年2月29日
    订单付款时间:2020年2月1日 至 2020年3月1日
    退款金额:付款后申请退款的金额。没有申请退款或没有付过款,退款金额为0

    导入部分库

    from plotly.offline import download_plotlyjs, init_notebook_mode
    init_notebook_mode(connected=True) 
    import pandas as pd
    import numpy as np
    import matplotlib.pyplot as plt
    import seaborn as sns
    import plotly as py
    import plotly.graph_objs as go
    pyplot = py.offline.iplot
    sns.set()
    plt.rcParams['font.sans-serif'] = ['SimHei']  
    plt.rcParams['axes.unicode_minus'] = False
    

    查看数据

    data = pd.read_csv('/home/mw/input/tmall6650/tmall_order_report.csv')
    data.head()
    

    先了解数据的基本情况

    data.info()
    

    可以看到有有7个字段,'订单付款时间’的非空计数少于28010,两个时间的数据类型不合适分析需要修改。

    数据预处理

    数据格式整理

    规范字段名
    索引某些字段时提示KeyError,怀疑字段名称不规范。

    修改前:

    data.columns
    
    Index(['订单编号', '总金额', '买家实际支付金额', '收货地址 ', '订单创建时间', '订单付款时间 ', '退款金额'], dtype='object')
    

    发现收货地址和订单付款时间后面存在空格。删除这些空格,规范字段名称。

    修改后:

    data.rename(columns={'收货地址 ': '收货地址', '订单付款时间 ':'订单付款时间'}, inplace=True)
    data.columns
    
    Index(['订单编号', '总金额', '买家实际支付金额', '收货地址', '订单创建时间', '订单付款时间', '退款金额'], dtype='object')
    

    修改时间的数据类型
    '订单创建时间’和’订单付款时间’的类型是object,将其改成日期格式,利用后期分析。

    data['订单创建时间'] = pd.to_datetime(data['订单创建时间'])
    data['订单付款时间'] = pd.to_datetime(data['订单付款时间'])
    

    增加分析要使用的字段

    data['创建时间'] = data['订单创建时间'].dt.strftime('%m月%d日')
    data['付款时间'] = data['订单付款时间'].dt.strftime('%m月%d日')
    data.head()
    

    # 创建函数,用来将weekday返回的数字转化为对应星期几。
    def to_weekday(a):
        result = np.nan
        if a == 0:
            result = '周一'
        elif a == 1:
            result = '周二'
        elif a == 2:
            result = '周三'      
        elif a == 3:
            result = '周四'
        elif a == 4:
            result = '周五'
        elif a == 5:
            result = '周六'
        elif a == 6:
            result = '周日'
        return result
    
    data['创建星期'] = data['订单创建时间'].dt.weekday.apply(to_weekday)
    data['创建时刻'] = data['订单创建时间'].dt.hour
    data['付款星期'] = data['订单付款时间'].dt.weekday.apply(to_weekday)
    data['付款时刻'] = data['订单付款时间'].dt.hour
    data.head()
    

    简化地址
    简化’收货地址’的省市名称,后续查看更方便。

    简化前:

    data['收货地址'].unique()
    
    array(['上海', '内蒙古自治区', '安徽省', '湖南省', '江苏省', '浙江省', '天津', '北京', '四川省',
           '贵州省', '辽宁省', '河南省', '广西壮族自治区', '广东省', '福建省', '海南省', '江西省', '甘肃省',
           '河北省', '黑龙江省', '云南省', '重庆', '山西省', '吉林省', '山东省', '陕西省', '湖北省',
           '青海省', '新疆维吾尔自治区', '宁夏回族自治区', '西藏自治区'], dtype=object)
    

    简化后:

    data['收货地址'] = data['收货地址'].str.replace('省','').str.replace('自治区','')
    data['收货地址'] = data['收货地址'].str.replace('壮族','').str.replace('维吾尔','').str.replace('回族','')
    data['收货地址'].unique()
    
    array(['上海', '内蒙古', '安徽', '湖南', '江苏', '浙江', '天津', '北京', '四川', '贵州', '辽宁',
           '河南', '广西', '广东', '福建', '海南', '江西', '甘肃', '河北', '黑龙江', '云南', '重庆',
           '山西', '吉林', '山东', '陕西', '湖北', '青海', '新疆', '宁夏', '西藏'], dtype=object)
    

    缺失值处理
    '订单付款时间’存在缺失值,缺失数量为:3923。

    sum(data['订单付款时间'].isnull())
    3923
    

    缺失比例为:14%。

    sum(data['订单付款时间'].isnull()) / data.shape[0]
    0.14005712245626561
    
    # 检查数据
    data[data['订单付款时间'].isnull() & data['买家实际支付金额']>0].size
    0
    

    检查得知’订单付款时间’为空的时候,实际付款金额都为0,没有出现错误数据。

    实际生活中存在下单但是不付款的情况,缺失比例也没有超出预期,判断不需要对其进行处理。

    异常值分析

    data.describe()
    

    从上表可以看到‘总金额’的最大值远远超过75%分位数,怀疑是异常值。

    画个箱线图辅助判断

    plt.boxplot(data['总金额'])
    plt.show()
    

    可以看到‘总金额’>175000的数据远离上极限,且25000到175000中间都是空白,判断总金额>175000的为异常值。

    检查该异常情况的数量:

    data[data['总金额'] > 175000]
    

    ‘总金额’大于175000的数据只有一条,并且没有付款,很有可能是乱点的,将其删除。

    data = data.drop(index=data[data['总金额'] > 17500].index)
    data.shape
    (28009, 13)
    

    '买家实际支付金额’异常值处理

    plt.boxplot(data['买家实际支付金额'])
    plt.show()
    

    查看实际支付金额大于6000的数据:

    data[data['买家实际支付金额'] > 6000]
    

    付款金额不是十分高,而且数量只有2,符合实际,不处理。

    退款金额’异常值处理

    plt.boxplot(data['退款金额'])
    plt.show()
    

    查看退款金额大于2000的数据:

    data[data['退款金额'] > 2000]
    

    退款金额=总金额,没有出现数据错误,而且数量少,符合生活中的实际情况,不处理。

    重复值处理

    np.sum(data.duplicated())
    0
    

    没有重复值。

    数据分析

    描述性统计

    '订单付款时间’为空的代表买家没有付款,对应的’买家实际支付金额’为0的在描述性统计时应当成空值而不是0;

    ‘退款金额’为0代表没有退款,在进行描述性统计时也应当成空值处理。

    data_desc = data.copy()
    data_desc['买家实际支付金额'] = np.where(data_desc['订单付款时间'].isnull(), np.nan, data_desc['买家实际支付金额'])
    data_desc['退款金额'] = data_desc['退款金额'].replace(0, np.nan)
    data_desc.describe()
    

    初步了解一下数据:

    订单情况
    共记录28009条订单,其中买家实际支付订单24087条(86.0%),买家有退款行为的订单5646条(占实际支付23.4%);
    订单总金额
    平均每单订单100.2元,金额最小1元,金额最大16065元;
    实际支付金额
    实际支付订单平均每单79.0元,金额最小0元,金额最大16065元;
    退款金额
    退款订单平均每单退款101.4元,金额最小1元,金额最大3800元。

    总体销售情况

    np.sum(data['买家实际支付金额'])
    1902487.15
    

    二月份的总销售额是190.25万

    GMV_day = data[data['付款时间'] != 'NaT'].groupby('付款时间').sum()
    order_day = data[data['付款时间'] != 'NaT'].groupby('付款时间').count()
    
    trace1 = go.Scatter(x=GMV_day.index, 
                        y=GMV_day['买家实际支付金额'], 
                        mode='lines',
                        marker=dict(color='orange'),
                        name='销售额'
                       )
    
    trace2 = go.Bar(x=order_day.index, 
                    y=order_day['订单编号'],
                    name='订单数',
                    opacity=0.7, 
                    marker=dict(color='steelblue'), 
                    yaxis='y2'
                   )
    
    layout = go.Layout(title='2020年2月销售额走势', 
                       xaxis=dict(tickangle=45, dtick=1), 
                       yaxis=dict(title='销售额(元)',zeroline=False), 
                       yaxis_tickformat='auto', 
                       yaxis2=dict(title='订单数', overlaying='y', side='right', showgrid=False),
                       annotations=[dict(x=0.1, xref='paper', y=0.95, yref='paper', text='二月份总销售额为190.25万',bgcolor='gainsboro',font={'size':13}, showarrow=False)],
                       legend=dict(x=0.1,y=0.85))
    
    trace = [trace1, trace2]
    fig = go.Figure(trace, layout)
    pyplot(fig)
    

    从上图我们可以得知以下信息:

    二月份总销售额为190.25万;
    2月16日以前销售额很少,仅在2月4日和2月9日达到两个小高峰,这两日的销售额为22000左右;
    2月10日至2月16日销售额仅有0~1000;
    2月17日开始销量持续增长,在2月25达到本月最高销售额(22.8万);
    3月1日的销售额突然变低,销售额只有298。
    问题分析:

    2月初销量低可能是因为春节假期,查阅可知,2020年的春节假期为1月24日至2月2日,但是当时正是疫情开始,复工复产时间推迟至不早于2月9日24时,2月10日-2月16日正好是销售最低迷的时间,应该是因为消费者正开始复工复产,无暇消费。
    3月1日销售额突降是因为该日订单只记录了在2月29日创建但在3月1日支付的,并不是3月1日的所有交易数据,在这里我们可以忽略3月1日。
    建议:

    2020年初疫情突发,导致本应在2月初春节结束恢复的销售,推迟至月中,甚至出现了日销量为0的情况。目前疫情已经常态化,应该准备多个应急方案,保证在突发事件发生时能及时举措,减少突发状况对销售的影响,如果竞争对手没有及时反应我司甚至能拔得头筹获得佳绩。
    接下来分析,我们可以针对哪些方面作出改进,增加销售。

    周趋势、日趋势分析

    上面我们分析了月度销售情况,接下来看看每周和每天的销售情况

    week_order = ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
    
    data_week_mean = [data.groupby('付款星期').sum()['买家实际支付金额'].loc[i] for i in week_order]
    data_week_count = [data.groupby('付款星期').count()['订单编号'].loc[i] for i in week_order]
    
    # 每个月有多少个周一、多少个周二是不固定的,如果单纯按周几分组求和,数量多的周x会占优。需要再求个平均。
    data_week_mean = np.divide(data_week_mean, [4,4,4,4,4,5,4]) # 当月有4个周一4个周二...
    data_week_count = np.divide(data_week_count, [4,4,4,4,4,5,4]) 
    
    data_hour_mean = data.groupby('付款时刻').sum()
    data_hour_count = data.groupby('付款时刻').count()
    
    # 绘制周销售趋势图
    trace_week1 = go.Scatter(x=week_order, 
                             y=data_week_mean, 
                             name='平均销售额',
                             marker=dict(color='orange')
                            )
    trace_week2 = go.Bar(x=week_order, 
                         y=data_week_count, 
                         name='订单数', 
                         opacity=0.7, 
                         marker=dict(color='steelblue'), 
                         yaxis='y2'
                        )
    trace_week = [trace_week1, trace_week2]
    layout1 = go.Layout(title='周销售趋势分析', 
                       yaxis=dict(title='销售额(元)'), 
                       yaxis2=dict(title='订单数', overlaying='y', side='right', showgrid=False),
                       legend=dict(x=0.9,y=1.4),
                       width=550, height=350)
    fig1 = go.Figure(trace_week, layout1)
    pyplot(fig1)
    
    # 绘制日销售趋势图
    trace_hour1 = go.Scatter(x=data_hour_mean.index, 
                             y=data_hour_mean['买家实际支付金额'], 
                             marker=dict(color='orange'), 
                             name='平均销售额', 
                            )
    trace_hour2 = go.Bar(x=data_hour_count.index, 
                         y=data_hour_count['订单编号'], 
                         opacity=0.7, 
                         marker=dict(color='steelblue'), 
                         name='订单数', 
                         yaxis='y2'
                        )
    trace_hour = [trace_hour1, trace_hour2]
    layout2 = go.Layout(title='日销售趋势分析', 
                       xaxis=dict(title='时刻', dtick=1, tickangle=0),
                       yaxis=dict(title='销售额(元)'), 
                       yaxis2=dict(title='订单数', overlaying='y', side='right', showgrid=False),
                       legend=dict(x=0.9,y=1.4),
                       width=550, height=350)
    fig2 = go.Figure(trace_hour, layout2)
    pyplot(fig2)
    

    从上图我们可以得到以下信息:

    每周销售最好的时候是周五,其次是周二,最差的是周一;
    周末并不是预想中销售最好的时间,甚至比大部分工作日差;
    凌晨销售量低,从6点开始销量稳定提升,中午开始趋于稳定略有波动,在10时、15时、21时分别有一个高峰,22点以后销量开始下降;
    销量最高的时间是晚上21点。
    建议:

    促销活动可以安排在周五开始,既可以提高原本的高销量,又可以拉动周末的消费;
    促销信息、产品推广广告的推送时间最好安排在晚上9点,此时消费人数最多,信息的曝光量最大,能带来最大收益;
    如果有条件多次推送信息,10点、15点、21点是较好的选择。

    产品价格分析

    因为数据集中即没有包含产品名称,也没有包含产品价格,我们姑且将订单总金额当成产品的价格,分析什么价格的产品更受消费者欢迎。

    sns.histplot(data['总金额'])
    plt.show()
    

    总金额500以上的数据虽然很少但刻度很大,包含进来严重拉伸了图形,不利于分析。

    筛选总金额500以内的数据,绘制直方图查看分布。

    plt.figure(figsize=(20,8),dpi=100)
    sns.histplot(data[data['总金额'] < 500]['总金额'])
    plt.xticks(np.arange(0,500,step=25), fontsize=20)
    plt.yticks(fontsize=20)
    plt.xlabel('订单金额',fontsize=20)
    plt.ylabel('订单数',fontsize=20, rotation=0, labelpad=40)
    plt.title('订单金额分布情况', fontsize=25)
    plt.show()
    

    可以看到大部分订单金额在200元以下,20~125居多。

    根据上图的分布,对订单总金额进行分组。

    price_max = data['总金额'].max()
    bins = [0, 20, 40, 60, 80, 100, 125, 150, 175, 200, 250, 300, 500, price_max]
    price_label = ['0-20', '20-40', '40-60', '60-80', '80-100', '100-125','125-150','150-175',
                   '175-200', '200-250', '250-300', '300-500', '500以上']
    price_cut = [pd.cut(data['总金额'], bins=bins, labels=price_label).value_counts()[i] for i in price_label]
    x = [i for i in range(len(price_label))]
    plt.figure(figsize=(10,8),dpi=100)
    sns.barplot(x=x, y=price_cut, palette='Blues_r')
    for i,j in zip(x, price_cut):
        plt.text(i, j+50, j, horizontalalignment='center',fontdict=dict(color='steelblue'))
        plt.text(i, j-280, '{:.1%}'.format(j/data.shape[0]), horizontalalignment='center',fontdict=dict(color='darkturquoise'))
    plt.xticks(ticks=x, labels=price_label)
    plt.tick_params(pad=10)
    plt.xlabel('订单金额', fontsize=12)
    plt.ylabel('订单数', rotation=0, labelpad=25, fontsize=12)
    plt.title('订单金额分布情况', fontsize=15)
    plt.show()
    

    从上图我们可以获得以下信息:

    大部分订单金额在200元以下,尤其是20~125元;
    其中以20-40元的订单量最多,占了总订单量的1/4;
    20元以下和175元以上的订单很少,加起来仅占总订单量的11%;
    即20-175元的订单占了总订单量的近90%。
    建议:

    产品推广以价格20-125元或20-175元的产品为主,尤其着重推广20-40元的产品,这个价格区间的产品是消费者最喜欢消费的。

    地区分析

    data_area = data.groupby('收货地址').sum()['买家实际支付金额'].sort_values(ascending=False).reset_index()
    
    plt.figure(figsize=(20,8), dpi=100)
    sns.barplot(x='收货地址', y='买家实际支付金额', data=data_area, palette='Blues_r')
    plt.xlabel('')
    plt.ylabel('销售额', rotation=0, labelpad=30, fontsize=15)
    plt.title('各省市销售额情况', fontsize=20)
    plt.show()
    

    from pyecharts.charts import Map
    from pyecharts import options as opts
    
    data_area_list = [list(i) for i in zip(data_area['收货地址'], np.round(data_area['买家实际支付金额']))]
    map = Map()
    map.add("销售额", data_area_list, maptype='china', is_map_symbol_show=True)
    map.set_global_opts(title_opts=opts.TitleOpts(title='各省市销售额'), 
                        visualmap_opts=opts.VisualMapOpts(min_=0, max_=270000, range_color=['#D7E3EF', '#163A69']),
                        legend_opts=opts.LegendOpts(is_show=False))
    map.render_notebook()
    

    从上面两张图我们可以知道以下信息:

    销售额最高的省市是上海,北京、江苏、广东、浙江为第二梯队;
    销售额高的省市主要集中在东部和南部沿海,以及四川省;
    销售额最少的省市为西藏、青海、湖北、新疆、宁夏, 主要为西部地区。
    分析:

    湖北此时处于疫情中心,销售额低属于情理之中;
    从上文得知,目前销量最高的产品价格集中在20-125元,尤其是20-40元。产品价格不高,而且往往对西部偏远省份不包邮,因而在考虑成本的情况下很难提升这些地区的销量,所以建议以提高西南、中部以及东北地区的销量为先,逐渐寻找发展西部地区销量的对策。
    建议:

    保持优势省市的订单量;
    西南、中部以及东北地区有很大的发展潜力,建议先从这些地区开始开展促销提高销量;

    转化率分析

    本项目中,用户行为路径为:创建订单 -> 订单付款 -> 订单成交 -> 订单全额成交

    # 计算各个阶段订单数
    data_create = data.shape[0]
    data_pay = data[~data['订单付款时间'].isnull()].shape[0]
    data_pay_part = data[data['买家实际支付金额'] > 0].shape[0]
    data_pay_all = data[data['买家实际支付金额'] == data['总金额']].shape[0]
    
    # 计算转化率
    data_funnel = pd.DataFrame()
    data_funnel['环节'] = ['下单', '付款', '成交', '全额成交']
    data_funnel['订单量'] = [data_create, data_pay, data_pay_part, data_pay_all]
    data_funnel['总体转化率%'] = np.round(data_funnel['订单量'] / data_funnel['订单量'][0], 3)*100
    data_funnel['付款订单转化率%'] = np.round(data_funnel['订单量'] / data_funnel['订单量'][1], 3)*100
    data_funnel.loc[0,'付款订单转化率%'] = np.nan
    data_funnel
    

    from pyecharts.charts import Funnel, Page
    page=Page()
    
    #绘制总转化率漏斗图
    funnel1 = Funnel(init_opts=opts.InitOpts(width="600px", height="400px"))
    funnel1.add(series_name="转化率", 
               data_pair=list(zip(data_funnel['环节'],data_funnel['总体转化率%'])),
               gap=2,
               label_opts=opts.LabelOpts(position='inside', formatter='{b}:{c}%'),
               tooltip_opts=opts.TooltipOpts(formatter='{a} <br/>{b} : {c}%'))
    funnel1.set_colors(colors=['#B0CDDD', '#5C96BB', '#3470A3', '#163A69'])
    funnel1.set_global_opts(title_opts=opts.TitleOpts(title='总体转化率', subtitle='相比总下单数'),
                            legend_opts=opts.LegendOpts(is_show=False))
    page.add(funnel1)
    
    # 绘制付款订单转化率漏斗图
    funnel2 = Funnel(init_opts=opts.InitOpts(width="600px", height="400px"))
    funnel2.add(series_name="转化率", 
                data_pair=list(zip(data_funnel['环节'][1:],data_funnel['付款订单转化率%'][1:])),
                sort_='none',
                gap=2,
                label_opts=opts.LabelOpts(position='inside', formatter='{b}:{c}%'),
                tooltip_opts=opts.TooltipOpts(formatter='{a} <br/>{b} : {c}%'))
    funnel2.set_colors(colors=['#ffd460', '#ffaa64', '#ff8264'])
    funnel2.set_global_opts(title_opts=opts.TitleOpts(title='付款订单转化率', subtitle='相比付款订单数'),
                            legend_opts=opts.LegendOpts(is_show=False))
    page.add(funnel2)
    page.render_notebook()
    



    从上面的漏斗图我们可以知道:
    下单订单的付款率为86%;
    付款订单里78.7%的订单支付金额大于0元;
    付款订单里76.6%的订单为全额成交,即有100%-78.7%=23.4%的订单存在退款行为;
    分析:

    78.7%与76.6%比较接近,说明绝大部分的退款行为是部分退款。
    付款订单里23.4%的订单存在退款行为,接近每4个订单就有一个有申请退款,其中绝大部分只退部分款项,这部分买家应该是保留了商品,但是对商品不太满意,只有很少买家对商品非常不满意或者不合适等原因全额退款;
    前文提到90%的订单商品价格在20-175元之间,中低的价格加上23.4%的退款率,说明我们的商品虽然具有价格优势,可是质量或其他方面存在令人不满意的地方(简单理解4个人里就有一个人对产品不满意,是比较严重的问题)。
    建议:

    23.4%的退款率说明商品存在的问题比较严重,应尽快找出问题所在(质量不达标、实物与图片不符、尺寸与标注不符、包装不好导致商品破损、快递运输过慢、发货出错、价格高于其他店铺…);
    计算后可知,假设退款率为0,销售额将能提高30%,当然实际并不可能达到退款率为0,但是假如解决了商品存在的问题后,退款率降至10%,销售额将能提高17%,退款率降得越低,销售额提升越多。

    # 假设退款率为0,销售额将能提高30%
    (data['退款金额'].sum() + data['买家实际支付金额'].sum()) / data['买家实际支付金额'].sum()
    1.3008356298227821
    

    总结

    2月总销售额为190.25万元。

    为了增加销售,可以从以下几方面开展行动:

    找到导致高退款率的原因并解决,这是目前最重要的事情;
    促销活动可以安排在周五开始;
    促销信息、产品推广广告的推送时间最好安排在晚上9点,其次是10点、15点;
    产品推广以价格20-125元或20-175元的产品为主,尤其着重推广20-40元的产品;
    保持优势省市的订单量,大力发展西南、中部以及东北地区的销量。

    来源:张国荣家的弟弟

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python数据分析实战 —— 天猫订单数据分析

    发表评论