数据分析——某东品牌奶粉数据

写在前面

项目准备

  • 语言:Python 3.9
  • IDE :Pycharm2020.2.3
  • 库:Pandas、Numpy、matplotlib、seaborn
  • 分析框架:5w2h、增长率法

代码描述

       首先,导入数据分析必要的库文件,以及使用一些魔法函数;

import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
import seaborn as sns    #数据可视化库

plt.rcParams['font.sans-serif'] =['Microsoft YaHei'] # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus']=False # 用来正常显示负号

       然后,通过pandas等库的方法了,对数据进行读取以及查看四分位数等一些简单处理;

def open_file():
    df_trade=pd.read_csv('csv_data_path')
    df_baby=pd.read_csv('csv_data_path')
    df=df_trade.merge(df_baby, how='outer', on='user_id')
    df['day']=df['day'].astype('str')
    df['day']=pd.to_datetime(df['day'], errors='ignore')

    #查看四分位和标准差
    print(df['buy_mount'].describe())
    print('-'*20)
    print(f'标准差为:{df["buy_mount"].std()}')

    # 处理销量异常值
    df.drop(index=df[df['buy_mount']>30].index,inplace=True)
    return df

       定义函数,查看各年度每月销售情况走势,其中要求必须要提取每月销量,计算同比增长速率,并且以可视化形式展现

def each_year_situation(df):
    '''
    观察各年度每月销售情况走势
    :param df:
    :return:
    '''
    df=df.groupby(pd.Grouper(key='day', freq='m')).sum()
    df['month']=df.index.month
    df['year']=df.index.year
    df=df[['year','month','buy_mount']]

    # 提取每月销量
    _x=df[df['year']==2014]['month']
    _x_2012=df[df['year']==2012]['month']
    _x_2015=df[df['year']==2015]['month']
    _y_2012=df[df['year']==2012]['buy_mount']
    _y_2013=df[df['year']==2013]['buy_mount']
    _y_2014=df[df['year']==2014]['buy_mount']
    _y_2015=df[df['year']==2015]['buy_mount']
    print(_y_2014.sum()/_y_2013.sum()-1)

    # 提取每月同比增速
    df_YOY=pd.DataFrame(
        {
            '2013':_y_2013.tolist(),
            '2014':_y_2014.tolist(),
        },
        index=_x
    )
    _y_2012.index=_x_2012
    _y_2015.index=_x_2015
    df_YOY=df_YOY.join(_y_2012)
    df_YOY=df_YOY.join(_y_2015,lsuffix='2012', rsuffix='2015')
    df_YOY.columns=['2013','2014','2012','2015']
    df_YOY=df_YOY.loc[:,['2012','2013','2014','2015']]
    df_YOY=df_YOY.pct_change(axis=1)*100

    plt.figure(figsize=(32,9),dpi=160)
    # 每月销量折线图
    ax1=plt.subplot(1,2,1)
    plt.plot(_x_2012, _y_2012, label='2012年')
    plt.plot(_x, _y_2013, label='2013年')
    plt.plot(_x, _y_2014, label='2014年')
    plt.plot(_x_2015, _y_2015, label='2015年')
    plt.title('月度销量情况')
    plt.grid(alpha=0.6)
    plt.xticks(_x,_x)
    plt.xlabel('月份')
    plt.ylabel('销量(件)')
    plt.legend(loc='upper left')

    # 同比增速折线图
    ax1=plt.subplot(1,2,2)
    _y_2015=df_YOY[df_YOY['2015']!=0]['2015']
    _x_2015=_x[:len(_y_2015.values)]
    plt.plot(_x,df_YOY['2013'],label='2013年')
    plt.plot(_x,df_YOY['2014'],label='2014年')
    plt.plot(_x_2015,_y_2015,label='2015年')
    plt.title('月度同比增速')
    plt.xticks(_x)
    plt.xlabel('月份')
    plt.ylabel('同比增速(百分比)')
    plt.legend()
    plt.grid(alpha=0.6)
    plt.show()
def YOY_2014(df):
    '''
    计算2014年同比增速
    :param df:
    :return:
    '''
    df=df.groupby(pd.Grouper(key='day',freq='Y')).sum()
    df['年同比增速']=df['buy_mount'].pct_change()
    print(df['buy_mount'])
    print(df['年同比增速'])
def situation_2015_2(df):
    '''
    查看各年度春节前30日销售情况走势
    :param df:
    :return:
    '''
    df=df.groupby(by=pd.Grouper(key=('day'),freq='D')).sum()['buy_mount']
    _y_2013=df['2013-1-10':'2013-2-15']
    _y_2014=df['2014-1-1':'2014-2-6']
    _y_2015=df['2015-1':'2015-2'][:-17:-1][::-1]
    _x=[i for i in range(len(_y_2013))]
    _x_label=['闰月初一', '闰月初二', '闰月初三', '闰月初四', '闰月初五', '闰月初六', '闰月初七', '闰月初八', '闰月初九', '闰月初十', '闰月十一', '闰月十二', '闰月十三', '闰月十四', '闰月十五', '闰月十六', '闰月十七', '闰月十八', '闰月十九', '闰月二十', '闰月廿一', '闰月廿二', '闰月廿三', '闰月廿四', '闰月廿五', '闰月廿六', '闰月廿七', '闰月廿八', '闰月廿九', '闰月三十', '正月初一', '正月初二', '正月初三', '正月初四', '正月初五', '正月初六', '正月初七', '正月初八']

    # 折线图-春节前30日每日销量情况比较
    plt.figure(figsize=(32,9),dpi=160)
    ax1=plt.subplot(1,2,2)
    plt.plot(_x,_y_2013,label='2013年')
    plt.plot(_x,_y_2014,label='2014年')
    plt.plot(_x[:len(_y_2015)],_y_2015,label='2015年')
    plt.xticks(_x,_x_label,rotation=45)
    plt.legend()
    plt.title('2013-2015年春节前30日每日销量情况比较')
    plt.grid(alpha=0.5)

    df_1=pd.DataFrame(
        {
            '2013':_y_2013[:16].sum(),
            '2014':_y_2014[:16].sum(),
            '2015':_y_2015.sum(),
        },
        index=[0]
    )
    df_1=pd.concat([df_1,df_1.pct_change(axis=1)])
    df_1.index=['销量','同比增速']
    _x=df_1.columns

    # 春节前30日-前13日销量情况比较
    ax2=plt.subplot(1,2,1)
    plt.bar(_x, df_1.loc['销量'],width=0.3,color='#ffaaa5',label='销量(左轴)')
    for x,y_2 in zip(_x,df_1.loc['销量']):
        plt.text(x, y_2+10, y_2,ha='center')
    ax2.set_ylabel('销量')
    ax2.set_xlabel('年份')
    plt.title('2013-2015年闰月初一到十五销量同比情况')
    plt.legend(loc='upper left')
    ax3=ax2.twinx()
    _y_YOY = round((df_1.loc['同比增速'] * 100),1)
    plt.plot(_x, _y_YOY, color='#a8e6cf',label='同比增速(右轴)')
    _y_YOY=_y_YOY.dropna()
    for x,y in zip(_y_YOY.index,_y_YOY):
        plt.text(x, y+0.1, f'{y}%',ha='left')
    ax3.set_ylabel('同比增速')
    plt.legend(bbox_to_anchor=(0,0.96),loc='upper left')
    plt.show()
def find_reason(df):
    df=df.groupby([pd.Grouper(key='day',freq='D'),'cat1']).sum()['buy_mount']
    df_2014=df.loc['2014-1-1':'2014-1-16'].unstack()
    df_2015=df.loc['2015-1-21':].unstack()
    df=pd.DataFrame({'2014':df_2014.sum(),'2015':df_2015.sum()},index=df_2015.columns)
    df['yoy']=df.pct_change(axis=1)['2015']*100

    _x=np.arange(len(df.index))
    width=0.35
    color=sns.color_palette('Blues',3)
    plt.figure(figsize=(16,9),dpi=160)
    ax=plt.subplot()
    rects1=plt.bar(_x-width/2,df['2014'],width,label='2014年销量(左轴)',color=color[0])
    rects2=plt.bar(_x+width/2,df['2015'],width,label='2015年销量(左轴)',color=color[1])
    ax.set_ylabel('销量')
    ax.set_xlabel('产品大类')
    plt.title('2014-2015年春节前30-14日各分类销量情况')
    plt.legend(loc='upper right')
    ax2=ax.twinx()
    plt.plot(_x, df['yoy'], label='同比增速(右轴)',color=color[2])
    ax2.set_ylabel('同比增速(%)')
    for x,y in zip(_x,round(df['yoy'],2)):
        plt.text(x, y+1, f'{y}%',ha='left')
    plt.legend(bbox_to_anchor=(1,0.9375),loc='upper right')
    plt.xticks(_x,df.index)
    plt.show()
def marketing_plan_2015(df):
    df=df.groupby(by=[pd.Grouper(key=('day'),freq='D'),'cat1']).sum()['buy_mount']
    # .unstack()方法将复合索引的series转化为dataframe
    # 此方法也可以用于dataframe的行列转换
    df_2013=df.loc['2013-1-10':'2013-2-15'].unstack()
    df_2014=df.loc['2014-1-1':'2014-2-6'].unstack()
    df_2015=df.loc['2015-1-21':].unstack()
    
    _x_label=['闰月初一', '闰月初二', '闰月初三', '闰月初四', '闰月初五', '闰月初六', '闰月初七', '闰月初八', '闰月初九', '闰月初十', '闰月十一', '闰月十二', '闰月十三', '闰月十四', '闰月十五', '闰月十六', '闰月十七', '闰月十八', '闰月十九', '闰月二十', '闰月廿一', '闰月廿二', '闰月廿三', '闰月廿四', '闰月廿五', '闰月廿六', '闰月廿七', '闰月廿八', '闰月廿九', '闰月三十', '正月初一', '正月初二', '正月初三', '正月初四', '正月初五', '正月初六', '正月初七', '正月初八']
    _x=range(df_2013.shape[0])
    _x_1=range(df_2015.shape[0])
    plt.figure(figsize=(32,27),dpi=160)
    for i in range(df_2015.shape[1]):
        ax=plt.subplot(3,2,i+1)
        plt.plot(_x_1, df_2015.iloc[:,i], label='2015')
        plt.plot(_x, df_2014.iloc[:,i], label='2014')
        plt.plot(_x, df_2013.iloc[:,i], label='2013')
        plt.xticks(_x,_x_label,rotation=45)
        plt.legend()
        plt.grid(alpha=0.6)
        plt.title(f'{df_2015.columns[i]}大类销量情况')
    plt.show()

       以上函数定义完毕,方便后续分析调用,并且在对数据梳理之后,能达到基本了解字段含义,然后梳理指标,开始分析

数据梳理

本次样本数据集位婴儿用品信息,包含两个表trade(商品交易记录)和babyinfo(婴儿信息)

  • trade表:29972行*7列
    • buy_mount (购买数量/销量)
    • user_id(用户id)
    • auction_id(购买行为编号)
    • cat1(商品所属的大类)
    • cat_id(cat1的子类,是更细分的类别)
    • property(商品属性)
    • day(购买时间)
  • babyinfo表:945行*3列
    • user_id(用户id)
    • birthday(出生日期)
    • gender:性别(0 男孩,1 女孩,2性别不明)

指标梳理

  • 结果指标:

    • 销量:buy_mount
  • 维度:

    • 用户id:user_id(babyinfo.user_id=trade.user_id)
    • 购买时间:day(2012年7月2日-2015年2月5日)
    • 商品类别:(大类:cat1;小类:cat_id;商品属性:property)
    • 婴儿年龄:day(购买时间) - birthday(出生日期)
    • 性别:gender

注意:此数据集的分析文章,大都会以最基础的1-7岁对婴儿年龄划分为7个组别。但实际上,针对不同年龄段的婴儿,婴幼儿奶粉分为4个阶段:

  • 1段:0-6个月(4-6月已可食用辅食)
  • 2段:6-12个月(6月大:可食用糊状或泥状的食物;9月大:可食用有硬度食物)
  • 3段:1-3岁
  • 4段:3-7岁(已经符合入读公办幼儿园的年龄,此阶段奶类流质食物已经不是主流)

这里凸显出一个问题:数据分析师习惯性以统计学含义理解指标,而不是找指标背后的业务含义

数据清洗

       数据清洗主要是针对异常值,这种异常值在电商平台较为常见,数据录入错误或者刷单行为都被认定为异常值,会影响某一阶段或者某款产品的销售局势等的判断,因此必须要进行异常值剔除。

df=open_file()    #得到标准差、四分位数、二分位数、最大值等信息

       利用四分位和方差对销量数据情况进行了解,确定异常值范围。站在统计学的角度,把超过平均值3倍标准差的销量(即2.54+64*3=194.54罐)作为异常值是常规的做法,但站在业务的角度则不合理。

       通过奶粉产品的净含量和各年龄段婴幼儿奶粉推荐食用量的估算,我们可以得到以下理论值数据:

       (1段、2段食用量分别参考自某品牌蓝臻婴儿配方奶粉 1段/2段,3段和4段的喂食频率改为每天1次,每次食用量依据为2段的单次用量)

       从上表中可以看出,用量最大的4段全年龄段理论上最多食用110罐400g的奶粉,一次性购买194罐以上才算异常值明显不合理。另外翻查2014年婴幼儿奶粉相关调研报告,400g的奶粉产品均价在250元左右,一次性购买100罐400g奶粉需要2万5千元,对于任何家庭来说,都是不合理支出结构。

       因此,衡量异常值,不能仅通过统计学意义,还需要结合业务的实际情况。

       在没有内部业务数据支撑下,以行业报告作为补充对异常值进行划分。根据国双2018年本土婴幼儿奶粉电商消费研究的数据,在电商平台购买婴幼儿奶粉的消费者年均购买次数约为27次,“双十一”、“618”两个购物节是囤货高峰。

       婴幼儿在0-1岁时,理论上一共需要81罐400g奶粉,假设用户除“双十一”、“618”外其他时间每次只购买1罐,那么两个购物节平均需要承担27罐奶粉,向上取整后,以单笔销量超过30罐奶粉作异常值处理。

确立标准

       本次数据集,没有过程指标,结果指标只有销量一个。接下来的分析都将围绕销量情况如何,导致目前状况的原因是什么以及提升空间在什么地方进行分析。

       通常我们会抛出这样的问题:现在的销量状况不好,要提高!!!(数据分析师日常用语)

       在这个问题当中,有3个非常关键的词:现在、不好、高,分别对应了5w2h里面的when(需要分析的时间段),what(标准)以及How much(改进效果)

       首先要确定的是经常会被混淆的名词/副词部分,即Who、Where、When部分。这里将时间范围锁定为2015年1月1日至2015年2月5日。

       第二步就是要解决标准的问题。数据分析报告常常被批没屁用,缺乏标准是原罪。“环比下降3%”是日报和周报里面经常出现的废话之一(有被冒犯到,谢谢),但如果给3%加上一个标准或者业务含义,比如这周是双十一,但销量环比下降3%,就变成很严重的业务问题了。

       因此,与利益相关人统一标准口径,对于数据分析至关重要,有了标准才能评判好坏优劣。在没有任何指标的情况下,可以采用趋势分析来确定指标,这里的标准定为当月同比增速必须高于上年同期同比增速或上年整体同比增速。

YOY_2014(df)    #得到2014年的同比增速为50.54%

        通过简单的计算,我们得知,2014年的同比增速为50.54%。那么这里可以把假定的问题翻译成具体的:2015年至今,销量同比增速低于目标的50.54%,需要将销量增速提至50.54%以上。

        在准备这次分析的过程中,也研究了几份相同数据集不同分析角度的文章,其中一篇开头看到15年整体的销量断崖式下降后,在没有进一步锁定问题爆发的具体时间点的情况下,便假设是某个用户群体的复购下降而导致。结果分析一轮回到月的时间维度上,才发现是因为数据记录不全而导致的销量骤降,做了不少无用功。

        多维度的分析,应该是一个金字塔式的分析路径:从一个维度的整体到局部,再引入另外一个维度的整体再到局部,而不是在多个维度间反复横跳。

初步规划的分析路径如下:

  • 1 观察各年度每月销量情况走势
  • 2 2015年1-2月的销量走势对比13年和14年,判断销量的好或差?
  • 3 如果销量差,问题出在什么地方
  • 4 如果销量差,还有多少缺口,有多少时间挽救,重要的挽救时间节点是什么时候?
  • 5 如果要冲销量,推广什么品类?

        观察上图,假设我们分析的目标时间段是e-f,两图的e-f都由15跌到13.5,但两种数据走势反映出的问题点是不一样的。左边是断崖式下降,要分析e-f之间出现了什么变化,而右边则是持续下降,只是e-f下降幅度较大,但关键的问题点在c-d-e。

        因此先对整体数据的走势有了印象,才能更好地把握住问题的关键点,避免管中窥豹。

实现思路:

  • 以购买日期为标准对数据进行分组聚合,并对时间进行降采样至月
  • 分别提取每年各月的销售数据
each_year_situation(df)    #得到结果0.5054245624186315

 

        观察数据可知,14年的销量走势与13年类似,并没有出现持续性下降的问题,因此可以把分析聚焦到各年度的1-2月数据进行分析。

        除了15年2月销量由于数据不全而出现骤降外,2013年的2月也同样出现了环比骤降的情况,第一反应是春节导致的下降。翻查13年-15年的春节(初一到初七)时间如下:

  • 2013年春节:2月9日-2月15日
  • 2014年春节:1月30日-2月6日
  • 2015年春节:2月19日-2月25日

         可以得知,15年的春节时间与13年类似,都是完整分布在2月,可初步推出,15年2月的销量数据应该与13年类似。如果把15年2月的目标定为同比增长50%显然不尽合理,因此我们将时间线修改为春节前30天。

2015年春节前销量情况

调用往年春节前30日的销量情况,确认目前销量是否良好,并推测未来14日的走势如何,是否需要进一步准备推广计划。

实现思路:

  • 以购买日期为标准对数据进行分组聚合,并对时间进行降采样至日
  • 利用日期对数据进行分段切片
  • 对分段数据进行求和并计算同比增速
situation_2015_2(df)

        由左图可以看到14年闰月初一到十五,同比增速为49.9%,而15年同时间段增速为43.2%,低于最低目标49.9%,销量状况不佳。

        进一步计算2014年春节前30日的总销量为1057,同比增速57.06%,得到2015年春节前30日目标销量为1057*1.499=1584罐,而目前总销量为1080罐,还有504罐的缺口,平均每天36罐。

原因拆解

        确定了现状及一级问题后,就要先着手分析问题的原因。但目前的问题是针对所有产品,人人都负责意味着没人负责,因此要进一步确认是那一条产品线出现问题。

find_reason(df)

        观察可知,50008168大类(后称168大类)、50014815大类(后称815大类)都是销量在前茅但增长幅度都非常的低,是主要的问题点。而122650008大类(后称08大类)的增速接近目标值,且销量占比低,是次要的问题点。

        这时候我们就可以拿着问题去跟这三条业务线相关负责人对线,把他们认为的原因作为新的假设,梳理成逻辑树的形式,再进行逐点通过分析数据验证,找到核心原因。这里的逻辑树采用营销中的两个经典模型:PEST和4P理论,但由于没有数据进一步支撑,不再进一步深挖原因。

推广计划分析

        在真实的商业场景中,我们可以根据上一步中找到的核心原因,再基于ROI(投入产出比)去作进一步的资源调配。但在这个案例当中,并没有更多的数据进行支撑,不过我们可以基于上一年的的数据,对今年未来14天的走势做基本预测,发掘可能挽救销售量的机会。

确定推广时限

        同样需要先了解整体销量走势,对比往年走势情况,才能预测出未来的销量走势会怎么样。

        从往期销售曲线可以看到,接下来的销量会逐步下降,且年廿十开始,每日销量降至20以下。假设年廿四到年三十日均销量为15,那么接下来一周日均销量要达到57罐。因此,需要在接下来的一周之内,作出1-2轮的推广计划,年廿四或廿五作为保底冲刺节点。

确定资源分配

        确定了推广计划的时间节点后,再考虑资源如何分配到不同的大类进行推广。这里需要分开不同产品来看销售曲线。

实现思路:

  • 1 对数据按大类和日期进行分类聚合
  • 2 通过时间戳进行切片获取相应时间段的数据
marketing_plan_2015(df)

        查看以日维度的销量数据,波动幅度会非常大,我们要知其然,更要知其所以然。因此要与相关业务部门确认上年同期的详细推广时间、内容、渠道等数据,才能准确地判断出,是自然增长还是推广带来的增长,从而给出更贴合实际的建议。

        28大类:2014年的数据当中,销量会有明显的波动周期,隔7-10天会出现一次陡增,需要对比推广计划,确定是活动推广周期还是自然增长。

  • 如果为自然增长,未来还会出现1-2次的陡增,单日销量估计在30左右。
  • 如果为活动推广所致,参考15年闰月初七至初九。在未来一周内进行一次同类推广活动,对数据监控后再作下一步推广计划
  • 作为今年销量最高的大类,可以选择其为推广计划中的核心产品

        38大类:2013-2015销量数据都不是很稳定,但2015年有个别日期销量猛增,同样结合今年的推广计划,反推推广是否能够促进38大类的销量。如果有效,可尝试增加一定推广的期限。

        168大类和815大类:根据过往数据,直到春节结束,销量会一直下降。参考过往这段时间是否有采取过推广计划。如果经过推广后依旧没有销量的增长,那么有富余资源的情况下再考虑推广这两类。

        520大类:除了520大类在14年的年初六有大幅的增长外,其余时间两大类的销量都是非常的低,日均销量为个位数。520大类在15年的销量几乎没有,可以考虑参考14年的年初六进行一次小规模推广。

        08大类:虽然看上去曲线很陡峭,但实际销量基本没超过个位数。结合过往推广计划进行判断,如果已经进行过推广,但销量依旧不乐观,则今年可以放弃08大类,不作推广。

汇报结果

        每当作总结报告的时候,数分们都会焦头烂额地四处找模板来弥补建议空洞的窘境,核心的问题是仅站在统计学的角度去分析,就数论数,没有把业务含义和业务逻辑融入分析当中。只要分析过程中,进一步深挖数据的业务含义,总结与建议便是一件水到渠成的事,只需把分析过程的结论按一定逻辑框架展现出即可。

  • 现状:春节前30日-16日共销售1080罐,同比增速为43.2%,略低于目标的50.54%,销售状况有待提高
  • 问题:距离春节还有14天,有504罐的销量缺口,未来一周日均需要销售57罐,年廿四到年三十日均销量需15罐才能达到目标。
  • 原因:168、815两个大类增速远低于目标值是核心原因,需要收集渠道等数据才能进一步定位更具体的原因
  • 做法:
    • 1 一周内做1-2论推广计划,年廿四或廿五作最后一波冲刺
    • 2 28大类可作为主推产品
    • 3 其次优先推广38大类、520大类
    • 4 在资源有剩余的情况下,再考虑推广168大类、815大类及08大类

总结&反思

        回顾整篇分析报告思路,有两点是突破结论只有环比下降3%的关键:

  • 数据分析不能局限于键盘内。一份有价值的分析报告是否具有价值,不是取决于ESP写得有多溜,而是取决于是否站在业务的角度去分析,以解决业务问题作为分析目标,以业务含义解读指标含义.ESP永远都只是工具

  • 思维和方法论才是业务型数据分析师的立身之本。一开始抓住问题的关键,有清晰的分析思路,才能通过数据为每一步行动找到支撑,而不是仅通过描述现状后给出一个“要搞高"这类没有营养的建议。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

加流罗

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值