简介:面向数据分析初学者与从业者,这份以智能手机价格数据集为对象的实操案例,完整演示了从数据清洗、整理、探索到可视化的分析流程。数据以CSV格式存储,涵盖品牌、型号、屏幕尺寸、处理器速度、内存、存储容量、摄像头规格、发布日期与价格等字段,可借助Excel、SPSS或Python进行多维度探索分析。压缩包共3个文件,包含原始CSV数据集、可交互查看的HTML可视化报告、记录完整分析步骤的IPython Notebook代码,整体仅1.07MB,轻量易下,目前已有189人浏览学习。通过运行附带的Jupyter代码,读者能生成价格分布、品牌价格对比、性能与价格关系等图表,直观揭示智能手机价格与品牌、配置等因素的关联,同时掌握数据预处理与可视化表达的核心技能,既辅助企业优化定价策略,也为消费者选购提供参考。
1. 从一个案例包看懂智能手机价格数据能分析出什么
搜到「智能手机价格数据集可视化分析(数据集+代码).rar」这种压缩包时,很多人的第一反应是解压、跑代码、看图。但真正让这个案例有价值的地方,不是画几张图,而是回答一个具体问题:为什么同一时期的智能手机,价格区间能差出好几倍?数据集里通常包含 RAM、电池容量、屏幕像素、摄像头参数、机身重量等硬件指标,以及一个代表价格档位的标签列。可视化分析要做的事情,就是把这些硬件指标和价格档位之间的关系用图表展现出来——哪项指标对价格影响最大,哪些指标之间有强相关性,哪些指标看似重要实则区分度很低。这篇文章按最常见的 Python + Pandas + Matplotlib/Seaborn 路线,把整套分析流程拆开讲:数据读入与清洗、价格分布可视化、特征相关性分析、区分度验证,最后落在可复现的代码写法上。适合刚接触数据分析项目、想拿现成数据集练手,或者准备把它改写成简历项目的开发者。
2. 数据集结构认知与 pandas 读入清洗:先摸清价格档位怎么来的
2.1 压缩包里常见的文件结构与字段命名
这个案例包通常包含一个 CSV 或 Excel 格式的手机规格表,外加一份 Jupyter Notebook 或.py脚本。数据集的使用方式是pd.read_csv()直接读入,但在读入之前,需要先确认字段含义,因为这类数据集没有标准字典,不同来源的命名略有差异。比较常见的列有:battery_power(毫安时)、blue(是否支持蓝牙)、clock_speed(主频 GHz)、dual_sim(是否双卡)、fc(前置摄像头像素)、four_g、int_memory(内部存储 GB)、m_dep(厚度)、mobile_wt(重量)、n_cores(核心数)、pc(主摄像头像素)、px_height/px_width(分辨率)、ram(内存 MB)、sc_h/sc_w(屏幕尺寸)、talk_time(续航)、three_g、touch_screen、wifi,以及目标列price_range,取值一般为 0、1、2、3,分别对应低成本、中成本、高成本、极高成本四档。
import pandas as pd df = pd.read_csv("smartphone_price.csv", encoding="utf-8") print(df.shape) print(df.dtypes) print(df["price_range"].value_counts().sort_index())df.shape看行数和列数,dtypes检查每个字段的类型是否合理。price_range的value_counts()用来确认四档样本量是否均衡——如果某一档只有几十条,后续画箱线图时该组的统计意义会打折扣。这种字段检查是可视化之前最容易被跳过的环节,但恰恰决定了后续图表的可信度。
2.2 缺失值、重复行与异常值的处理策略
这类公开数据集通常已经做过基本清理,但并不能因此跳过检查。缺失值处理上,数值型字段可以先用isna().sum()统计缺失数量;如果缺失占比低于 5%,常见做法是用中位数填充,因为手机硬件参数往往存在偏态分布,均值会被极端值拉偏。重复行用df.duplicated().sum()检查,有重复直接drop_duplicates()去掉。
# 缺失值统计 missing = df.isna().sum() print(missing[missing > 0]) # 中位数填充 for col in ["battery_power", "ram", "px_height"]: if df[col].isna().any(): df[col] = df[col].fillna(df[col].median()) # 去重 df = df.drop_duplicates().reset_index(drop=True) # 异常值检查:看价格档位中 ram 的最小/最大值 print(df.groupby("price_range")["ram"].agg(["min", "max", "median"]))注意reset_index(drop=True)的作用:去重后索引会出现空洞,不重置的话,后续iloc或按索引合并时容易踩坑。异常值不用急着删,先看分组统计——比如高价格档出现 RAM 极低的情况,可能是真实低配高价机型,也可能是数据录入错误。这类个案建议单独拿出来观察,而不是直接剔除,保留异常值有时反而能发现更有意思的业务问题。
2.3 把价格档位从标签转化为可解释的分组
price_range是整数标签,可视化时需要把它映射成可读的字符串分组,方便图例和标题展示。映射关系可以根据数据集自带的说明文档确认,如果没有说明,常见的对应关系是:0 对应「低端」,1 对应「中端」,2 对应「高端」,3 对应「旗舰」。需要注意,这个映射只是语义化,不改变原始数据的数值,千万不要把它转成category类型后参与数值运算。
price_map = {0: "low", 1: "mid", 2: "high", 3: "flagship"} df["price_label"] = df["price_range"].map(price_map) # 按组统计常用硬件指标的中位数 group_summary = df.groupby("price_label")[ ["ram", "battery_power", "px_height", "mobile_wt"] ].median().round(0) print(group_summary)分组统计表格是可视化的前置参考:如果不同价格档位的 RAM 中位数差异明显,说明该字段与价格高度相关,值得重点画图;如果差异不大,则要考虑它是否有交互作用——比如ram与int_memory组合起来才能体现机型定位。groupby中传入列表来选择多个列,返回的是一个新的 DataFrame,使用round(0)是为了让打印结果更清爽,实际运算不受影响。
3. 价格分布可视化的三张核心图:直方图、箱线图与相关性热力图
3.1 用直方图看连续字段的整体分布形态
第一类图表是单变量分布图,观察 RAM、电池容量、屏幕分辨率等字段的分布形态。使用matplotlib的hist或seaborn的histplot都可以。偏态分布很常见:电池容量和 RAM 往往集中在几个标准档位,比如 2000mAh、3000mAh、4000mAh,对应不同价位段的公版方案。
import matplotlib.pyplot as plt import seaborn as sns plt.rcParams["font.sans-serif"] = ["SimHei", "Arial Unicode MS"] plt.rcParams["axes.unicode_minus"] = False fig, axes = plt.subplots(1, 2, figsize=(14, 5)) sns.histplot(df, x="ram", bins=30, kde=True, ax=axes[0], color="#4C72B0") axes[0].set_title("RAM 分布直方图", fontsize=14) axes[0].axvline(df["ram"].median(), color="red", linestyle="--", label="中位数") sns.histplot(df, x="battery_power", bins=30, kde=True, ax=axes[1], color="#DD8452") axes[1].set_title("电池容量分布直方图", fontsize=14) axes[1].axvline(df["battery_power"].median(), color="red", linestyle="--", label="中位数") plt.tight_layout() plt.savefig("dist_hists.png", dpi=150) plt.show()bins=30控制分组数量,数据集样本量在 1000 到 2000 条时,30 个 bin 能保留细节又不会太碎。kde=True叠加核密度曲线,帮助判断分布是否双峰——如果 RAM 分布出现双峰,往往意味着数据集中混入了明显的高端和低端两批机型。axvline画中位数参考线,用来跟均值对比,判断是否右偏。中文字体设置是必须做的,否则坐标轴标题会显示成方块;Linux 环境如果没装 SimHei,需要换成系统支持的中文字体或者直接用英文标签。
3.2 箱线图对比不同价格档位的字段差异
第二类图表是分组箱线图,这是验证某个硬件指标对价格区分度的最直接方式。看ram和price_label的关系:如果四个箱体位置依次抬高且重叠很少,说明 RAM 是一个强区分特征;如果箱体重叠严重,说明该特征不足以单独解释价格差异。
fig, axes = plt.subplots(1, 2, figsize=(14, 5)) order = ["low", "mid", "high", "flagship"] sns.boxplot(data=df, x="price_label", y="ram", order=order, ax=axes[0], palette="Blues", showfliers=False) axes[0].set_title("各价格档位 RAM 对比箱线图") sns.boxplot(data=df, x="price_label", y="battery_power", order=order, ax=axes[1], palette="Greens", showfliers=True) axes[1].set_title("各价格档位电池容量对比箱线图") plt.tight_layout() plt.savefig("box_group_compare.png", dpi=150) plt.show()order参数显式指定箱子顺序,否则 seaborn 会按字母序排列,成图后解读起来会乱。showfliers=False表示不显示离群点,适合 RAM 这种本身分布集中、偶有极端值的字段;电池容量那幅图保留离群点,是为了观察是否存在低端机配大电池这种反直觉个案。箱线图的信息密度很高:箱子位置看中位数差异,箱体宽度看四分位距,须的长度看尾部分布。中途如果发现某个价格档位的箱子明显比别的高且窄,说明该档位内部配置趋同,定价策略上更依赖品牌而非硬件。
3.3 相关性热力图识别字段间的共线关系
第三类图表是相关性热力图,用于识别硬件指标之间的线性相关性。价格档位与 RAM 的相关性普遍最高(通常能到 0.9 以上),而px_height与px_width之间存在天然强相关,因为分辨率由两者共同决定。
num_cols = ["battery_power", "ram", "px_height", "px_width", "int_memory", "mobile_wt", "n_cores", "price_range"] corr = df[num_cols].corr() plt.figure(figsize=(10, 8)) sns.heatmap(corr, annot=True, fmt=".2f", cmap="RdBu_r", center=0, square=True, mask=np.triu(np.ones_like(corr, dtype=bool), k=1)) plt.title("硬件参数与价格档位相关性热力图") plt.tight_layout() plt.savefig("corr_heatmap.png", dpi=150) plt.show()annot=True显示数值,fmt=".2f"保留两位小数。mask参数隐藏上三角,避免重复展示。center=0表示颜色映射以 0 为中点,正相关显示为红色系,负相关为蓝色系,视觉上更直观。解读热力图的关键不是找最大数,而是看结构:如果ram和int_memory相关系数也很高,后续做回归或分类模型时要考虑多重共线性问题。热力图本身不做因果判断,它只告诉你「哪些变量倾向于一起变化」,但在这个数据集里,它已经足以支撑结论——RAM 是与价格档位线性关系最强的硬件指标。
4. 特征区分度检验:散点矩阵与分组统计交叉验证
4.1 用散点矩阵观察多变量交互与价格档位边界
相关性热力图只能描述两两线性关系,当需要同时看三个以上变量时,seaborn.pairplot是更直接的工具。把ram、battery_power、px_height和price_label同时放进散点矩阵,每个子图按价格档位着色,能直观看到不同档位在二维平面上是否可分。
sample_df = df.sample(n=200, random_state=42) sns.pairplot( sample_df, vars=["ram", "battery_power", "px_height"], hue="price_label", hue_order=["low", "mid", "high", "flagship"], palette="Set1", diag_kind="kde", plot_kws={"alpha": 0.6, "s": 15}, ) plt.savefig("pairplot_sampled.png", dpi=150) plt.show()sample(n=200, random_state=42)不是必须的,但如果原始数据有 2000 条,散点矩阵会画出几十万个点,PDF 或 PNG 渲染时文件体积大、开图慢,抽样后读图体验更好。random_state=42固定随机种子,保证每次运行抽到的样本一致,这是复现性的基本要求。diag_kind="kde"让对角线显示密度曲线而不是直方图,曲线重叠部分越少,说明该特征对价格档位的区分能力越强。看图时重点观察同类颜色的点是否聚在一起、不同颜色之间是否边界清晰——RAM 字段通常能看到明显的横向分层,而battery_power的点会混在一起,说明仅凭电池容量很难判断价格。
4.2 分组统计验证可视化结论:数值不能只靠眼睛
散点矩阵和箱线图给出的是视觉证据,视觉结论还需要数值交叉验证。计算每个价格档位下各字段的均值、标准差,并使用百分位数判断重叠程度,是验证「某个特征对价格有区分度」的常用方法。
verification = ( df.groupby("price_label")["ram"] .agg(["count", "mean", "median", "std", lambda x: x.quantile(0.25), lambda x: x.quantile(0.75)]) .round(0) ) verification.columns = ["数量", "均值", "中位数", "标准差", "P25", "P75"] print(verification) # 检查相邻档位的 P75 与 P25 是否重叠 for i in range(3): low_tier = ["low", "mid", "high", "flagship"][i] high_tier = ["low", "mid", "high", "flagship"][i + 1] overlap = min(verification.loc[high_tier, "P75"], verification.loc[low_tier, "P75"]) - max( verification.loc[high_tier, "P25"], verification.loc[low_tier, "P25"]) print(f"{low_tier} 与 {high_tier}: 四分位距重叠量 = {overlap:.0f}")这段代码的意义在于把「看图觉得有区分度」转成「数值上不重叠」。四分位重叠量为负数时,说明两个价格档位之间至少有 75% 的样本在 RAM 上完全分开,这是一个非常强的验证信号。lambda x: x.quantile(0.25)是自定义聚合函数的写法,pandas 的agg接受可调用对象,所以这种匿名函数可以直接传入。如果后续要写进报告,可以把输出数值放在箱线图下方作为图注。
4.3 可视化大屏思路对这份案例的意义
热词里频繁出现「可视化大屏」,但这份价格数据集不太适合直接做大屏,原因是它没有时间维度和地理维度,缺少大屏最常用的两个叙事轴。如果想把案例往大屏方向扩展,需要自己构造维度:比如把price_range映射为不同色阶,用卡片展示各档位占比,用雷达图对比旗舰机与低端机的多维度均值差距。这种改造成本不高,但能让项目展示界面更有视觉冲击力。常规做法是用plotly生成交互式图表,再用flask或streamlit包一层网页服务。「企业级数据可视化」通常强调的不只是美观,而是信息层级——先看总量,再看分组,最后看异常值,这个顺序不要打乱。
5. 复现一份完整分析报告:输出配置、随机种子与常见卡点
5.1 图表输出的统一样式与高清保存参数
分析做完后,图表往往要放进报告或幻灯片。统一样式能省去大量后期处理时间,推荐在脚本开头设置全局绘图参数:
plt.rcParams.update({ "figure.dpi": 120, "savefig.dpi": 200, "font.size": 11, "axes.spines.top": False, "axes.spines.right": False, })savefig.dpi=200适合 16:9 幻灯片插图,文字边缘清晰但文件体积可控。figure.dpi=120控制屏幕显示时的默认分辨率,分析过程中缩放窗口不会糊。去掉上边框和右边框是现代的图表风格,比默认的全边框更干净。如果图表数量超过六张,建议按功能分文件保存:fig/dist_hists.png、fig/box_compare.png、fig/corr_heatmap.png、fig/pairplot.png,目录在代码里用os.makedirs("fig", exist_ok=True)自动创建。
5.2 操作时最常见的三个报错与对应解法
第一类报错是UnicodeDecodeError: 'utf-8' codec can't decode byte。原因是压缩包里的 CSV 可能是 GBK 或 Latin-1 编码,读入时指定encoding="gbk"或encoding="latin-1"即可。第二类报错是 seaborn 的palette传入了无效值,新版 seaborn 对调色板名称校验更严格,统一使用palette="Blues"这种注册名最稳妥。第三类报错在df["price_range"].value_counts()阶段不明显,而是在画热力图时由于列名包含中文导致matplotlib字体告警,处理方式只有一种:把plt.rcParams["font.sans-serif"]设置为已安装的中文字体,然后执行plt.rcParams["axes.unicode_minus"] = False。
5.3 复现时务必固定三处随机数
这个案例的代码里包含随机元素时,必须固定随机种子才能保证别人的运行结果与你看过的一致。三处最容易遗漏的随机源是:df.sample()抽样、train_test_split划分训练集、模型初始化参数。即便当前代码里没有建模环节,只要后续加了分类器或聚类,随机种子的差异就会导致图表中的散点分布不同。建议在脚本入口统一设置:
import numpy as np np.random.seed(42) random_state = 42代码写完后,用python script.py完整跑一遍,确认输出目录下生成的图片与print结果一致。在 Jupyter Notebook 里逐格运行通过的代码,不代表脚本化后还能跑通——最常见的问题是plt.show()阻塞或%matplotlib inline失效。笔记转脚本时,把plt.show()替换为plt.savefig()并加plt.close(),就能避免保存了大量相似图片却不小心关掉当前窗口的问题。
本文还有配套的精品资源,点击获取