城市餐饮店铺数据处理与可视化分析

1,541 阅读7分钟
github地址:github.com/jiahaoabc/C…

目的和背景

目的

对数据进行处理,从‘口味’、‘环境’、‘服务’、‘消费’、‘性价比’这些条件入手,对不同种类餐饮店铺进行各类对比分析,分析上海人的餐饮喜好。

数据背景

基于上海市的一份餐饮数据


总数据量为96389

数据存在的问题

粗略看了下数据,比较明显的问题有数据缺失以及数据异常(比如口味评分、环境评分等等均为0)


数据初步清洗

读取数据

df1=pd.read_excel('上海餐饮数据.xlsx')

去除空值

df1.dropna(inplace=True)
print('去除缺失值后剩余数据量:',len(df1))

去除空值后剩下96255条数据

去除异常值

在粗略查看数据的时候,发现'口味','环境','服务'这三者的数据要么同时为异常值0,要么同时全部正常。为了证明这一猜想,查看'口味','环境','服务'这三者的正常值的数据量分别为多少

len(df1[df1['口味']>0]),len(df1[df1['环境']>0]),len(df1[df1['服务']>0])
(78583, 78583, 78583)

数量一样,可以判定上面的猜测是正确的,因此去掉'口味','环境','服务'这三者其中一种的异常数据,另外两者异常数据也会跟着去除。这里我们对异常值采取删除处理,

去掉'口味','环境','服务','人均消费'异常的数据

data=df1[(df1['服务']>0)&(df1['人均消费']>0)]
print(len(data))

清洗后剩下54886条正常数据

数据转换

从'口味'、环境'、'服务'以及'人均消费'这其中的一个单一变量不方便判断一家店铺整体的整体情况,中国人比较热衷于性价比,用性价比来评判一家店铺的整体情况不失为一个好办法。对于一家餐饮店铺,相信大部分人的关注程度应当是口味>环境>服务(至少我个人是这样),所以分配的权重比为:

口味:环境:服务=5:3:2

这里计算性价比为:

性价比=(口味评分*0.5+环境评分*0.3+服务评分*0.2)/人均消费

data['性价比']=(data['口味']*0.5+data['环境']*0.3+data['服务']*0.2)/data['人均消费']

数据进一步处理

初步箱型图

def boxplot(data,*args):
    fig,axes=plt.subplots(1,len(args),figsize=(15,6))
    for i in range(0,len(args)):     
        data.boxplot(column=[args[i]],ax=axes[i])
boxplot(data,'口味','人均消费','性价比')#查看‘口味(环境、服务)’、‘人均消费’、‘性价比’箱型图


此时的数据和图简直不忍直视。。。,存在不少极端的数据,有些‘人均消费’数值甚至在五六千以上(虽然可能存在这么贵,但这样的数据会干扰数据整体拟合度,需要去掉)

再次去除异常值

def remove_outliers(data,col):
    Q1=data[col].quantile(q=0.25)#下四分位数
    Q3=data[col].quantile(q=0.75)#上四分位
    IQR=Q3-Q1#四分位数全距
    if col=='口味':
        down_limit=Q1-1.5*IQR#下温和异常值边界
        up_limit=Q3+1.5*IQR#上温和异常值边界
    else:
        down_limit=Q1-3*IQR#下极度异常值边界
        up_limit=Q3+3*IQR#上极度异常值边界
    print(col,'下上四分位,下上异常边界值分别为:',Q1,Q3,down_limit,up_limit)
    clean_data=data[(data[col] > down_limit)&(data[col] < up_limit)]
    return clean_data
data_clean=remove_outliers(data,'口味')
data_clean=remove_outliers(data_clean,'人均消费')
data_clean=remove_outliers(data_clean,'性价比')

这里说明一下为何‘人均消费’和'性价比’没有去除温和异常值,首先'口味'的数值集中在0-10之间,所以它的数据是比较集中的,可直接去除所有异常值。

'人均消费'和'性价比’的各个数值'如下:

人均消费 下上四分位,下上极度异常边界值分别为: 24.0 87.0 -165.0 276.0
性价比 下上四分位,下上异常边界值分别为: 0.0925974025974026 0.3090909090909091 -0.5568831168831169 0.9585714285714285

人均消费和性价比有些数据比较高,但是不一定是错误数据,有些店铺的消费确实比较多,有些店铺也可能真的很划算,所以此时应该扩大以下正常值范围,‘包容’一些温和异常值(价格在276以下的,性价比在0.96以内的),只去掉一些极度异常的值

清洗后箱型图

boxplot(data_clean,'口味','人均消费','性价比')


虽然看着还存在不少异常值,这些异常值是上面说到的留下的温和异常值,这些都是可以接受的。

此时还剩下53059条正常数据

数据分组

对各类型菜品店铺进行分组

def data_groupby(data,col):
    data_group=data[['类别','口味','人均消费','性价比']]
    data_group=data_group.groupby(col).mean() #求平均值
    return data_group
data_group=data_groupby(data_clean,'类别')

前五行数据如下:


数据归一化

对数据进行归一化处理,易于明白数据差异和大小。

def data_norm(data,*args):
    for i in range(0,len(args)):
        col_name=args[i]+'_norm'
        data[col_name]=(data[args[i]]-data[args[i]].min())/(data[args[i]].max()-data[args[i]].min())
    return data
data_deal=data_norm(data_group,'口味','人均消费','性价比')
columns=['口味','口味_norm','人均消费','人均消费_norm','性价比','性价比_norm']#改变列名顺序
data_deal=data_deal[columns]
#大部分中国人喜欢划算,我们以性价比排序
data_deal.sort_values(by='性价比_norm',inplace = True, ascending=False)
data_deal.head()
data_deal.tail()

性价比最高五行数据如下:


性价比倒数五行数据如下:


数据可视化

这里选择bokeh作为可视化工具

模块导入以及画布基本设置

导入相关模块

from bokeh.plotting import figure,show,output_file
from bokeh.models import ColumnDataSource
from bokeh.models.annotations import BoxAnnotation
from bokeh.models import HoverTool
from bokeh.layouts import gridplot
import copy

标签改为英文,添加size字段(下面散点图点大小)、拷贝两个对象生图

data_deal.index.name='type'
data_deal.columns=['kw','kw_norm','price','price_norm','xjb','xjb_norm']
data_deal['size'] = data_deal['kw_norm'] * 40  # 添加size字段作为点的大小
data_deal_2=copy.deepcopy(data_deal)
data_deal_3=copy.deepcopy(data_deal)

设置工具、标签

hover = HoverTool(tooltips=[("餐饮类型", "@type"),
                            ("人均消费", "@price"),
                            ("性价比得分", "@xjb_norm"),
                            ("口味得分", "@kw_norm")
                           ])  

餐饮类别—人均消费—口味—性价比关系散点图

散点图生图代码

source=ColumnDataSource(data_deal)
result = figure(plot_width=800, plot_height=250,
                title="餐饮类型得分情况" ,
                x_axis_label = '人均消费', y_axis_label = '性价比得分', 
                tools=[hover,'box_select,reset,xwheel_zoom,pan,crosshair']) 
# 构建绘图空间
result.circle(x='price',y='xjb_norm',source=source,
             line_color='red',line_dash=[6,4],fill_alpha = 0.6,
        size = 'size')

不同餐饮‘人均消费’、‘口味’、‘性价比’之间的关系。每个点分别代表一类餐饮,圆圈大小表示口味评分,圆圈越大,味道越好

口味—类别关系柱状图

图形生成代码

data_deal_2.sort_values(by='kw_norm',inplace=True,ascending=False)#口味评分从大到小牌
data_type_2=data_deal_2.index.tolist()
source2=ColumnDataSource(data_deal_2)
kw=figure(plot_width=800,plot_height=250,title='口味得分',x_range=data_type_2,
          tools=[hover,'box_select,reset,xwheel_zoom,pan,crosshair'])
kw.vbar(x='type',top='kw_norm',source=source2,width=0.9,alpha=0.8,color='black')


人均消费—类别关系柱状图

图形生成代码

data_deal_3.sort_values(by='price_norm',inplace=True,ascending=False)
data_type_3=data_deal_3.index.tolist()
source3=ColumnDataSource(data_deal_3)
price = figure(plot_width=800, plot_height=250, title='人均消费得分',x_range=data_type_3,
              tools=[hover,'box_select,reset,xwheel_zoom,pan,crosshair'])
price.vbar(x='type', top='price_norm', source=source3,width=0.9, alpha = 0.8,color = 'red') 


可视化结论

根据数据,总的来说

在所有菜系中,南菜(南方小菜)、素菜、火锅最迎合上海人的口味,味道浓烈的北菜(东北菜)和辛辣的湘菜(湖南菜)使得他们无法消受,因此可以发现上海人饮食较为清淡。

口味评分最低的是常菜,家常菜里缺乏了亲人的味道以及一家人的其乐融融,或许再厉害的大厨做出的家常菜都难以获得消费者的青睐吧。

甜点、快餐、面馆占据性价比前三甲,虽然口味得分较低,但物价高涨、生活节奏紧张的上海,使得这三类低价,便捷的食物在所有餐饮食物里脱颖而出。

令人惊讶的是,疆菜(新疆菜/清真菜)这一菜系无论是从口味、人均消费还是性价比来看都获得了上海人民的认可。

助餐、蟹宴、午茶性价比最低,吃着蟹宴,喝着下午茶,消费不考虑性价比和价格,大概这就是有钱上海人的快乐吧。