温馨提示:源码获取请扫描文末 QQ 名片 :)
餐饮业生意好坏的影响因素通常有很多,包括店铺菜系、口味、服务态度、周边环境、人口密度、所在区域、人均消费等等方面。本项目以上海城市为例,对其餐饮业消费数据进行统计分析,从三个维度“口味”、“人均消费”、“性价比”对不同菜系进行横向比较。针对某一商铺类型,将上海划分成格网空间,做空间指标评价,基于聚类算法,得到较好选址的网格位置的中心坐标,以及所属区域。
基于聚类算法的城市餐饮数据分析与店铺选址系统的功能组成如下图所示:
1. 从三个维度“口味”、“人均消费”、“性价比”对不同菜系进行比较,并筛选出可开店铺的
2. 选择一个餐饮类型,将上海划分成格网空间,做空间指标评价,得到餐饮选址位置
本项目数据统计分析主要采用 numpy 和 pandas,可视化采用 Matplotlib、seaborn 和 bokeh实现:
import os
import gc
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib as mpl
import matplotlib.pyplot as plt
np.random.seed(7)
plt.style.use('fivethirtyeight')
import warnings
warnings.filterwarnings('ignore')
from bokeh.plotting import figure,show,output_file
from bokeh.models import ColumnDataSource
from bokeh.layouts import gridplot
from bokeh.models import HoverTool
from bokeh.models.annotations import BoxAnnotation
from bokeh.io import output_notebook
from matplotlib.font_manager import FontProperties
# 读取字体路径,设置字体为思源黑体
myfont=FontProperties(fname=r'/System/Library/Fonts/Hiragino Sans GB.ttc')
sns.set(font=myfont.get_name())
output_notebook()
读取上海餐饮数据和上海地区的人口密度数据,并进行缺失值分析:
data_df = pd.read_csv('上海餐饮数据.csv', encoding='gbk')
data_df = data_df.drop(columns=['城市'])
data_df = data_df[data_df['人均消费'] > 0]
people_df = pd.read_csv('上海地区人口密度数据.csv', encoding='gbk')
people_df.columns = ['人口密度','道路长度','餐饮计数','素菜餐饮计数','Lng','Lat']
people_df.fillna(0,inplace = True)
def contains_null(dataframe):
missing_df = dataframe.isnull().sum(axis=0).reset_index()
missing_df.columns = ['column_name', 'missing_count']
missing_df['missing_rate'] = 1.0 * missing_df['missing_count'] / dataframe.shape[0]
missing_df = missing_df[missing_df.missing_count > 0]
missing_df = missing_df.sort_values(by='missing_count', ascending=False)
return missing_df
可以发现,缺失的比例非常小,考虑直接删除这些异常数据。
plt.figure(figsize=(16, 5))
sns.kdeplot(data_df['点评数'])
plt.title('上海商铺点评数分布', fontsize=16, weight='bold')
plt.show()
plt.figure(figsize=(16, 17))
plt.subplot(311)
sns.distplot(data_df['口味'], bins=50)
plt.title('上海商铺<口味>评分分布', fontsize=16, weight='bold')
plt.subplot(312)
sns.distplot(data_df['环境'], bins=50)
plt.title('上海商铺<环境>评分分布', fontsize=16, weight='bold')
plt.subplot(313)
sns.distplot(data_df['服务'], bins=50)
plt.title('上海商铺<服务>评分分布', fontsize=16, weight='bold')
plt.show()
可以看出:
plt.figure(figsize=(16, 10))
plt.subplot(211)
sns.distplot(data_df['人均消费'], bins=50, color="green")
plt.title('上海商铺人均消费分布', fontsize=16, weight='bold')
plt.subplot(212)
tmp = data_df[(data_df['人均消费'] > 0) & (data_df['人均消费'] < 200)]
sns.distplot(tmp['人均消费'], bins=100, color="red")
plt.title('上海商铺人均消费分布', fontsize=16, weight='bold')
plt.show()
可以看出:
plt.figure(figsize=(16, 5))
sns.countplot(data_df['类别'],
order=data_df['类别'].value_counts().index)
plt.title('上海不同区域商铺菜系类别的数量分布', fontsize=16, weight='bold')
plt.show()
plt.figure(figsize=(16, 5))
sns.countplot(data_df['行政区'])
plt.title('上海不同区域商铺的数量分布', fontsize=16, weight='bold')
plt.show()
# 去除坐标异常值
data_df = data_df[data_df['Lng'] > 120]
plt.figure(figsize=(14, 10))
plt.scatter(data_df['Lng'], data_df['Lat'], color="blue")
plt.title('上海商铺的地理位置分布')
plt.show()
data_df['性价比'] = (data_df['口味'] + data_df['环境'] + data_df['服务']) / data_df['人均消费']
plt.figure(figsize=(16, 5))
plt.subplot(121)
sns.distplot(data_df['性价比'], bins=50, color="green")
plt.title('上海商铺性价比指数分布', fontsize=16, weight='bold')
plt.subplot(122)
sns.distplot(data_df[data_df['性价比'] < 5]['性价比'], bins=50, color="blue")
plt.title('上海商铺性价比指数分布', fontsize=16, weight='bold')
plt.show()
可以看出,大部分商铺的性价比评分集中在 3 以内,性价比超高10的只有16个商铺,占比非常小,可能是数据采集的时候存在问题,考虑删除该异常数据。
plt.figure(figsize=(16, 10))
sns.violinplot(x='性价比', y='类别', data=data_df, scale='width', orient='h')
plt.show()
可以看出,甜点、快餐、美食、面馆的性价比相对较高!
# 人口密度指标标准化
people_df['rkmd_norm'] = (people_df['人口密度']-people_df['人口密度'].min())/(people_df['人口密度'].max()-people_df['人口密度'].min())
# 餐饮热度指标标准化
people_df['cyrd_norm'] = (people_df['餐饮计数']-people_df['餐饮计数'].min())/(people_df['餐饮计数'].max()-people_df['餐饮计数'].min())
# 同类竞品指标标准化
people_df['tljp_norm'] = (people_df['素菜餐饮计数'].max()-people_df['素菜餐饮计数'])/(people_df['素菜餐饮计数'].max()-people_df['素菜餐饮计数'].min())
# 道路密度指标标准化
people_df['dlmi_norm'] = (people_df['道路长度']-people_df['道路长度'].min())/(people_df['道路长度'].max()-people_df['道路长度'].min())
people_df['人口密度系数'] = people_df['rkmd_norm']*0.4 + people_df['cyrd_norm']*0.3 + \
people_df['tljp_norm']*0.1 + people_df['dlmi_norm']*0.2
# 人口密度按照从大到小排序
people_df = people_df.sort_values(by = '人口密度系数',ascending=False).reset_index()
# 商铺数据按照性价比排序
data_df = data_df.sort_values(by = '性价比',ascending=False).reset_index(drop=True)
按照人口密度绘制空间散点图:
people_df['size'] = people_df['人口密度系数'] * 15
people_df['color'] = 'green'
people_df['color'].iloc[:200] = 'blue' # 人口密度最大的前10个位置
# 添加size字段
source = ColumnDataSource(people_df)
# 创建ColumnDataSource数据
hover = HoverTool(tooltips=[("经度", "@Lng"),
("纬度", "@Lat"),
("人口密度系数", "@rkmd_norm"),
]) # 设置标签显示内容
p = figure(plot_width=800, plot_height=800,
title="空间散点图" ,
tools=[hover,'box_select, reset, wheel_zoom, pan,crosshair'])
# 构建绘图空间
p.square(x = 'Lng',y = 'Lat',source = source,
line_color = 'black',fill_alpha = 0.5,
size = 'size',color = 'color')
p.ygrid.grid_line_dash = [6, 4]
p.xgrid.grid_line_dash = [6, 4]
show(p)
绘制综合性价比的 TOP1000商铺的空间分布:
# 添加显示的size字段
people_df['size'] = people_df['人口密度系数'] * 15
people_df['color'] = 'green'
people_df['color'].iloc[:20] = 'blue' # 人口密度最大的前20个位置
cater_df = data_df[:1000] # 选择性价比排名前 1000 名的商铺进行可视化
cater_df['size'] = data_df['性价比'] * 3
cater_df['color'] = 'yellow'
cater_df['color'].iloc[:20] = 'red' # 选择性价比排名前 1000 名的商铺设置为红色
# 创建 ColumnDataSource 数据
people_source = ColumnDataSource(people_df)
cater_source = ColumnDataSource(cater_df)
p = figure(plot_width=800, plot_height=800, title="上海地区人口密度分布;商铺位置分布" )
# 构建绘图空间
p.square(x = 'Lng',y = 'Lat',source = people_source,
line_color = 'black',fill_alpha = 0.5,
size = 'size',color = 'color')
p.square(x = 'Lng',y = 'Lat',source = cater_source,
line_color = 'black',fill_alpha = 0.5,
size = 'size',color = 'color')
p.ygrid.grid_line_dash = [6, 4]
p.xgrid.grid_line_dash = [6, 4]
show(p)
可以看出,市中心人口密度最大,大部分的商铺也集中在市中心,说明人口密度是商铺选址考虑的重要因素!
利用 kmeans 对商铺的经纬度、行政区、点评数、口味、环境、服务和人均消费等特征进行聚类,对聚类结果可视化:
colors = ['#d40045','#ff7f00','#055D87','#56007d','#af0065','#fff231','#ca1028','#d9760f','#a38204', '#232166']
# 添加显示的size字段
people_df['size'] = people_df['人口密度系数'] * 15
people_df['color'] = 'green'
cater_df = cater_df[:1000] # 选择性价比排名前 1000 名的商铺进行可视化
cater_df['size'] = data_df['性价比'] * 4
cater_df['color'] = 'yellow'
for i in range(N_CLUSTER):
cater_df['color'].loc[cater_df['cluster_label'] == i] = colors[i]
# 创建 ColumnDataSource 数据
people_source = ColumnDataSource(people_df)
cater_source = ColumnDataSource(cater_df)
hover = HoverTool(tooltips=[("经度", "@Lng"),
("纬度", "@Lat"),
("建议选址的中心点经度", "@cluster_Lng"),
("建议选址的中心点纬度", "@cluster_Lat")
]) # 设置标签显示内容
p = figure(plot_width=800, plot_height=800, title="上海地区人口密度分布;商铺位置分布",
tools=[hover,'box_select, reset, wheel_zoom, pan,crosshair'])
# 构建绘图空间
p.square(x = 'Lng',y = 'Lat',source = people_source,
line_color = 'black',fill_alpha = 0.2,
size = 'size',color = 'color')
p.square(x = 'Lng',y = 'Lat',source = cater_source,
line_color = 'black',fill_alpha = 0.9,
size = 'size',color = 'color')
p.ygrid.grid_line_dash = [6, 4]
p.xgrid.grid_line_dash = [6, 4]
show(p)
本项目以上海城市为例,对其餐饮业消费数据进行统计分析,从三个维度“口味”、“人均消费”、“性价比”对不同菜系进行横向比较。针对某一商铺类型,将上海划分成格网空间,做空间指标评价,基于聚类算法,得到较好选址的网格位置的中心坐标,以及所属区域。