上海餐饮大数据分析实战项目
简介:“上海餐饮数据.zip.zip”是一个涵盖上海市餐饮业多维度信息的综合性数据集,包含餐馆基本信息、菜品价格、用户评价、营业时间、地理坐标、消费档次、菜系分类及交易行为等数据。该数据以压缩格式存储,可能包含CSV、Excel或JSON等多种结构化文件,适用于市场研究、商业智能与学术分析。通过描述性统计、聚类分析、关联规则挖掘和时间序列预测等方法,结合Python、Tableau等工具进行可视化与建模,本项目旨在深入挖掘餐饮市场规律,洞察消费者行为,辅助企业优化运营策略,并为城市餐饮业态研究提供数据支持。
上海餐饮数据的深度挖掘与商业洞察
你有没有想过,为什么某些餐厅门口总是排着长队,而有些同样装修精致的地方却门可罗雀?在一座拥有2500万人口、超16万家餐馆的城市里——上海,答案从来不是“味道好”三个字就能概括的。这背后藏着海量数据之间的隐秘关联:地理位置如何影响评分分布?一条差评真的能让一家店“社死”吗?火锅配啤酒是偶然还是必然?
我们手头这份“上海餐饮数据集”,乍看只是冷冰冰的CSV和JSON文件,但当你真正潜入其中,会发现它像一张巨大的神经网络,连接着消费心理、空间逻辑与时间节奏。今天,咱们就一起拆解这张网,用Python做手术刀,一层层剖开它的肌理。
数据长什么样?别急,先看看它的骨架
打开 上海餐饮数据.zip ,你会发现三张核心表:
- restaurants.csv :餐馆的基本档案卡
- reviews.json :用户的喜怒哀乐实录
- menu.xlsx :每道菜背后的利润密码
它们通过一个共同字段—— restaurant_id ——像齿轮一样咬合在一起,形成典型的星型结构。这种设计很聪明,既避免了重复存储,又方便多维分析。
import pandas as pd
# 加载数据
rest_df = pd.read_csv("restaurants.csv")
review_df = pd.read_json("reviews.json")
menu_df = pd.read_excel("menu.xlsx")
print(f"餐馆数:{len(rest_df)} | 评论数:{len(review_df)} | 菜品数:{len(menu_df)}")
输出可能类似:
餐馆数:8743 | 评论数:126,982 | 菜品数:210,345
哇,快13万条评论!平均每家店有近15条反馈。不过等等……是不是所有数据都靠谱?比如那个写着“人均消费¥0”的米其林候选餐厅?还有经纬度为(0,0)的“幽灵门店”?这些可不是小瑕疵,而是足以让整个模型跑偏的“毒药”。
所以问题来了: 你怎么判断一份数据到底值不值得信?
数据质量评估:别让“脏数据”毁了你的结论
业内有个经典说法:“Garbage in, garbage out.”(垃圾进,垃圾出)。哪怕你用的是BERT或Transformer,输入的是错乱标签,输出也只能是漂亮的废话。
我们得建立一套评估体系。这里推荐一个三维框架: 完整性、一致性、准确性 。
先来查查“缺胳膊少腿”的情况
missing_ratio = rest_df.isnull().mean() * 100
missing_ratio[missing_ratio > 0].sort_values(ascending=False)
假设结果如下:
avg_cost 12.7%
open_hours 10.3%
cuisine_type 6.8%
rating 3.2%
location 1.5%
看到没,“人均消费”居然有超过一成的数据缺失!而且注意,这类缺失很可能不是随机的——高端餐厅更不愿意标价,这是一种典型的 非随机缺失(MNAR) 。如果你直接拿均值去填,等于把一家本该人均800的私房菜强行拉低到200,这合理吗?
再看 open_hours ,格式五花八门:“10:00-22:00”、“每天营业”、“周一休息”、“见大众点评”。这种非结构化文本,机器根本没法处理。
还有坐标异常点。有人图省事,把未知位置全设成(0,0),导致地图上出现诡异的“黑洞聚集区”。要是你拿这个做热力图,黄浦江边上突然冒出一堆餐馆,那可就闹笑话了。
所以,在动手清洗之前,得先问自己几个灵魂拷问:
- 缺失是有规律的吗?
- 异常值是录入错误,还是真实存在的极端案例?
- 文本字段是否需要统一编码规范?
只有搞清楚这些问题,才能决定下一步怎么走。
清洗实战:从“脏乱差”到“干净整洁”
数据清洗听起来枯燥,但它就像炒菜前的备料——葱姜蒜切好、肉片腌制到位,后面大火快炒才不会翻车。
缺失值处理:别只会填均值!
说到填补空缺,很多人第一反应就是 .fillna(df['col'].mean()) 。但兄弟,时代变了!
根据Rubin的经典理论,缺失机制分三种:
| 类型 | 特点 | 处理建议 |
|---|---|---|
| MCAR(完全随机) | 和任何变量无关 | 均值/众数填充OK |
| MAR(随机依赖) | 可由其他变量预测 | 回归/KNN填充更优 |
| MNAR(自身决定) | 本身就说明问题 | 慎重!需建模解释 |
比如“人均消费”在高端餐厅中普遍为空,这就是典型的MNAR。这时候你应该思考:能不能用评分、评论数量、菜系类型这些信息来预测它?
Scikit-learn里的 IterativeImputer 就是干这个的:
from sklearn.experimental import enable_iterative_imputer
from sklearn.impute import IterativeImputer
# 挑选相关特征
features = ['rating', 'review_count', 'cuisine_encoded']
imputer = IterativeImputer(max_iter=10, random_state=42)
rest_df[features] = imputer.fit_transform(rest_df[features])
它像个侦探,反复推理各个变量之间的关系,直到推断出最合理的数值。虽然慢一点,但比瞎猜靠谱多了。
而对于分类变量如 cuisine_type ,可以用众数填充,但要小心扭曲分布。如果某类菜系本身占比就不高,硬塞进去反而会造成偏差。
异常值检测:哪些“离谱”其实是真相?
什么叫异常?有人觉得人均10万元肯定不对;但也有可能,那是外滩某家私人会所的真实定价。
所以我们不能一刀切地删掉,而要学会区分“噪声”和“信号”。
常用的工具有三个:
- IQR法 (箱线图规则):适合偏态分布
- Z-score :要求接近正态分布
- 孤立森林 (Isolation Forest):高维稀疏数据之王
来看一段实战代码:
from sklearn.ensemble import IsolationForest
import numpy as np
cost = rest_df['avg_cost'].dropna().values.reshape(-1, 1)
iso_forest = IsolationForest(contamination=0.05, random_state=42)
preds = iso_forest.fit_predict(cost)
outliers = cost[preds == -1]
print(f"孤立森林标记了 {len(outliers)} 个异常点")
contamination=0.05 意思是:“我预估大约5%的数据是异常的”,相当于给算法一个心理预期。
但重点来了: 检测完之后干嘛?
直接删除?不行。应该先人工审核!
flowchart LR
Start[开始检测] --> Detect[运行IQR/Z/IF检测]
Detect --> Count{异常数量 < 阈值?}
Count -- Yes --> Review[人工审核候选列表]
Count -- No --> Alert[触发警报通知负责人]
Review --> Judge{是否合理?}
Judge -- 是 --> Keep[保留原始值]
Judge -- 否 --> Fix[修正或删除]
Fix --> Log[记录日志]
Keep --> Log
Log --> End[更新数据集]
比如一家日料店人均980元,听着吓人,但人家卖的是蓝鳍金枪鱼大腹,真有可能。而一家沙县小吃显示人均500块?多半是单位搞错了(把“角”当“元”输进去了),就得纠正。
记住一句话: 数据清洗不是追求完美无瑕,而是确保逻辑自洽。
文本标准化:让机器也能读懂“好吃得飞起”
餐馆名字、地址、营业时间……这些都是文本字段,乱得很。有的写“麦当劳(南京东路店)”,有的写“麦當勞 南京东路”,还有的夹杂emoji 🍔🍟。
我们得统一标准:
def clean_text(text):
if pd.isna(text):
return text
# 转半角字符
text = unicodedata.normalize('NFKC', text)
# 去除多余空格
text = ' '.join(text.split())
return text.strip()
rest_df['name'] = rest_df['name'].apply(clean_text).str.title()
顺带提一句, str.title() 能把“kfc”变成“Kfc”,虽然不够完美(理想是“KFC”),但至少比全小写强。
至于营业时间,原始数据可能是这样:
"10:00-22:00""休息""周一至周五 11:00-21:00"
我们需要把它转成可用的时间区间:
from datetime import time
def parse_hours(s):
if not s or '休息' in s:
return None, None
try:
start, end = s.strip().split('-')
return time.fromisoformat(start), time.fromisoformat(end)
except:
return None, None
rest_df[['open_time', 'close_time']] = pd.DataFrame(
rest_df['business_hours'].apply(parse_hours).tolist()
)
有了这两个字段,就能进一步计算每日营业时长:
rest_df['duration_hrs'] = (
pd.to_timedelta(rest_df['close_time'].astype(str)) -
pd.to_timedelta(rest_df['open_time'].astype(str))
).dt.total_seconds() / 3600
现在你知道哪家店熬得最晚了吧?夜猫子食堂了解一下 😴
特征工程:别只盯着原始字段,要学会“造新零件”
清洗完的数据只是原材料,真正的战斗力来自 特征构造 。
加权评分:别再被“五星好评”忽悠了!
你有没有发现,很多新开业的小店动不动就4.9分,而老字号反而只有4.3?这是因为评分系统有个致命缺陷: 样本量越小,波动越大 。
解决办法是什么?引入贝叶斯加权!
参考IMDb的公式:
$$
\text{Bayesian Score} = \frac{R \cdot v + C \cdot m}{v + m}
$$
其中:
- $ R $:该餐厅的平均评分
- $ v $:评论数量
- $ C $:全局平均评分
- $ m $:最小评论阈值(比如取90%分位数)
实现起来很简单:
C = rest_df['rating'].mean()
m = rest_df['review_count'].quantile(0.9)
rest_df['bayesian_score'] = (
(rest_df['rating'] * rest_df['review_count']) +
(C * m)
) / (rest_df['review_count'] + m)
这样一来,那些只有两三个好评的“虚假繁荣”就被拉下来了。真正经得起考验的高分选手才会浮出水面。
空间连接:每家店到底属于哪个区?
光有经纬度还不够,我们要知道这家店是在静安寺还是陆家嘴。
这就需要用到GeoPandas了:
import geopandas as gpd
from shapely.geometry import Point
# 加载行政区划边界
districts = gpd.read_file("shanghai_districts.shp")
# 构造餐馆点位
geometry = [Point(xy) for xy in zip(rest_df['lng'], rest_df['lat'])]
gdf = gpd.GeoDataFrame(rest_df, geometry=geometry, crs="EPSG:4326")
# 执行空间连接
joined = gpd.sjoin(gdf, districts, how='left', predicate='within')
从此以后,每家店都有了明确的行政归属。你可以轻松回答这些问题:
- 哪个区的人均消费最高?
- 浦东新区 vs 徐汇区,谁的餐饮密度更大?
- 黄浦区的平均评分是不是显著高于郊区?
这才是真正的“属地化运营”基础。
用户评价分析:数字背后的喜怒哀乐
评分只是一个数字,但用户写的评论才是情绪的真实流露。
评分分布偏了吗?用偏度说话!
画个直方图看看:
sns.histplot(rest_df['rating'], bins=30, kde=True)
plt.title("上海餐馆评分分布")
plt.xlabel("平均评分(满分5)")
plt.ylabel("频次")
plt.show()
print(f"偏度:{skew(rest_df['rating']):.3f}")
如果输出是 -1.874 ,说明严重左偏——绝大多数餐厅集中在4.0以上,低于3.5的基本看不见。
这意味着什么?
👉 不满的顾客更倾向于沉默,而不是打低分。
👉 高分泛滥导致区分度下降,4.5分已经不算特别优秀了。
所以在做组间比较时,别急着用t检验。先做个Shapiro-Wilk正态性检验:
from scipy.stats import shapiro
stat, p = shapiro(rest_df['rating'].dropna())
if p < 0.05:
print("拒绝正态假设 → 改用Mann-Whitney U检验")
否则你的统计结论可能会翻车。
情感分析:从“不错”到“差劲透顶”,机器能懂吗?
中文情感分析是个 tricky 的活儿。光靠关键词匹配容易误判,比如:
- “服务还不错,就是价格贵了点” → 实际倾向中性偏负
- “真是‘惊喜’,厕所都在厨房里” → 明明是讽刺,却被当成正面词
怎么办?两条路:
方法一:词典+规则(轻量级)
用BosonNLP或知网HowNet的情感词典,加上否定词、程度副词的规则处理:
def calc_sentiment(text):
words = jieba.lcut(text)
score = 0
negations = ['不', '没', '无']
intensifiers = {'非常': 2.0, '很': 1.5, '太': 1.8}
for i, w in enumerate(words):
base = sentiment_dict.get(w, 0)
if i > 0 and words[i-1] in negations:
base *= -1
if i < len(words)-1 and words[i+1] in intensifiers:
base *= intensifiers[words[i+1]]
score += base
return score
优点是快、透明;缺点是覆盖有限,遇到新词就歇菜。
方法二:BERT微调(精准但重)
HuggingFace的 bert-base-chinese 模型,经过少量标注数据微调,就能理解上下文语义:
from transformers import BertTokenizer, BertForSequenceClassification
tokenizer = BertTokenizer.from_pretrained('bert-base-chinese')
model = BertForSequenceClassification.from_pretrained('bert-base-chinese', num_labels=3)
# 训练过程略,需准备标注数据
虽然训练费时间,但它能准确识别转折句、反讽句,效果远胜规则法。
📌 建议策略:前期用词典法快速跑通流程,后期用BERT提升精度。
空间热点探测:哪里才是真正“吃货天堂”?
你以为餐馆多的地方就是美食中心?不一定。关键要看是不是形成了 高质量聚集区 。
核密度估计(KDE):一眼看出“密度洼地”
import seaborn as sns
sns.kdeplot(data=rest_df, x='lng', y='lat', fill=True, cmap="Reds")
plt.title("上海餐馆核密度热力图")
plt.show()
颜色越深,表示单位面积内餐馆越多。你会发现几个明显的红色高亮区:人民广场、徐家汇、中山公园……
但这只是数量上的密集,不代表质量高。
Getis-Ord Gi*指数:找寻“高评分集群”
这才是高手玩法。它不仅能告诉你哪里评分高,还能判断是不是被一群同样高水平的邻居包围着。
from esda.getisord import G_Local
from libpysal.weights import DistanceBand
w = DistanceBand.from_dataframe(gdf, threshold=0.01) # 1公里内为邻域
g_local = G_Local(gdf['bayesian_score'], w, permutations=999)
gdf['hotspot'] = np.where(g_local.Zs > 1.96, 'Hot Spot',
np.where(g_local.Zs < -1.96, 'Cold Spot', 'Not Significant'))
结果可以分为四类:
| 类型 | 含义 | 商业建议 |
|---|---|---|
| Hot Spot(高-高) | 高分店扎堆 | 可入驻,借势品牌集群效应 |
| Cold Spot(低-低) | 差评店集中 | 警惕,可能存在区域口碑塌方 |
| High-Low | 孤立高分店 | 有机会成为“破局者” |
| Low-High | 周围都好就它差 | 可能存在经营问题,加强监管 |
想象一下,你要开一家新川菜馆,是挤进已经卷成麻花的徐家汇,还是选择尚未爆发但潜力十足的北外滩?答案不言而喻。
时间序列预测:下个月销售额能涨多少?
除了空间,时间维度也不能放过。
餐饮行业的销售曲线充满节奏感:周末高峰、节假日飙升、天气变化……传统ARIMA模型已经有点力不从心。
LSTM登场:捕捉复杂周期模式
长短期记忆网络(LSTM)擅长处理长期依赖关系。我们可以用过去30天的销量预测未来7天:
from keras.models import Sequential
from keras.layers import LSTM, Dense
def create_sequences(data, seq_length):
xs, ys = [], []
for i in range(len(data) - seq_length):
x = data[i:i+seq_length]
y = data[i+seq_length]
xs.append(x)
ys.append(y)
return np.array(xs), np.array(ys)
# 归一化
data = sales_series.values.astype('float32')
scaled = (data - data.mean()) / data.std()
X, y = create_sequences(scaled, 30)
X = X.reshape(X.shape[0], X.shape[1], 1)
# 构建模型
model = Sequential([
LSTM(50, return_sequences=True, input_shape=(30, 1)),
LSTM(50),
Dense(1)
])
model.compile(optimizer='adam', loss='mse')
model.fit(X, y, epochs=50, batch_size=32)
当然,LSTM也有缺点:训练慢、黑盒、难解释。所以更稳妥的做法是 融合模型 ——用ARIMA拟合趋势,LSTM预测残差。
关联规则挖掘:炸鸡为什么总配可乐?
菜单数据分析最有意思的部分来了: 菜品组合规律 。
使用FP-Growth算法,我们可以找出哪些菜经常一起出现:
from mlxtend.frequent_patterns import fpgrowth, association_rules
# one-hot编码订单数据
te = TransactionEncoder()
te_ary = te.fit(transactions).transform(transactions)
df_hot = pd.DataFrame(te_ary, columns=te.columns_)
# 挖掘频繁项集
frequent_itemsets = fpgrowth(df_hot, min_support=0.02, use_colnames=True)
# 生成规则
rules = association_rules(frequent_itemsets, metric="lift", min_threshold=1.2)
看看结果:
| antecedents | consequents | lift |
|---|---|---|
| 炸鸡 | 奶茶 | 2.60 |
| 小龙虾 | 冰啤酒 | 2.73 |
| 牛排 | 红酒 | 3.15 |
| 寿司 | 清酒 | 2.90 |
| 烤串 | 花生毛豆 | 2.93 |
看到没?“牛排→红酒”的提升度高达3.15,说明这不是巧合,而是强烈偏好。高端餐厅完全可以推出“主厨 pairing 推荐”,提升客单价。
甚至还可以按人群细分:
- 高消费群体:推荐“鹅肝+勃艮第红酒”
- 年轻上班族:打包“汉堡+薯条+可乐”套餐
- 夜宵党:自动捆绑“烤串+冰啤+花生”
这才是真正的个性化营销。
最后的话:数据驱动的本质,是闭环思维
讲了这么多技术细节,最后想说点务虚的。
数据分析的价值,从来不在于画出了多漂亮的图表,也不在于用了多高级的模型。真正的价值,在于能否形成一个 从洞察到行动的闭环 。
就像这张图:
flowchart TD
RawData[原始数据] --> Cleaning[清洗建模]
Cleaning --> Analysis[多维挖掘]
Analysis --> Insight[商业洞察]
Insight --> Action[运营动作]
Action --> Result[业务结果]
Result --> Feedback[反馈回数据]
Feedback --> RawData
每一次促销活动的效果,都会沉淀为新的评论和销量数据;每一次选址调整,都会改变空间分布格局。只要这个环一直转下去,企业的决策就会越来越聪明。
所以,下次当你面对一份看似平凡的数据集时,别急着跑模型。先问问自己:
这些数字背后,藏着什么样的人性、地理与时间密码?
一旦你想通了这个问题,代码自然就有了灵魂 💡
简介:“上海餐饮数据.zip.zip”是一个涵盖上海市餐饮业多维度信息的综合性数据集,包含餐馆基本信息、菜品价格、用户评价、营业时间、地理坐标、消费档次、菜系分类及交易行为等数据。该数据以压缩格式存储,可能包含CSV、Excel或JSON等多种结构化文件,适用于市场研究、商业智能与学术分析。通过描述性统计、聚类分析、关联规则挖掘和时间序列预测等方法,结合Python、Tableau等工具进行可视化与建模,本项目旨在深入挖掘餐饮市场规律,洞察消费者行为,辅助企业优化运营策略,并为城市餐饮业态研究提供数据支持。
更多推荐

所有评论(0)