资讯动态

数据分析师笔试题解析:异常值、聚类、SQL与AB测试实战

发布时间:2026/9/19 13:27:52 来源:尧图企业网站定制
简介这份《数据分析笔试题》PDF是一份面向数据分析师求职与技能自测的练习资料内容取自互联网公司真实笔试场景涵盖统计学基础、异常值识别、聚类分析、SQL取数与销售数据解读等核心模块。资源以经典例题带动知识点讲解例如Grubbs检验判断异常值、k-means聚类流程、按用户提取首个访问URL的SQL写法以及针对周末销售偏低原因的运营改进思路能帮助读者快速对照自身知识短板。包体为单份PDF文件大小218KB轻量易读适合笔试前集中复习或日常查漏补缺。截至目前已有472人学习可用于评估数据获取、业务理解和分析方法应用等综合能力对正在准备数据挖掘或数据分析岗位笔试的读者有较高参考价值。1. 从阿里数据分析师笔试题看岗位能力地图这份数据分析笔试题集把阿里巴巴、腾讯、百度、网易、搜狐五家公司的真实笔试题目凑在一起恰好拼出了数据分析岗完整的能力地图统计学检验、聚类算法、SQL 取数、业务归因、试验设计、工程算法六块一个不少。新入行的人别把它当成普通题库去背答案建议按章节对能力缺口做自测能否说清 Grubbs 检验的适用前提、能否用窗口函数改写取首个 URL 的 SQL、能否给销售数据的周末低谷做归因、能否把 AB 测试的抽样和检验步骤讲完整这些才是真正的准入门槛。做了两三年数据分析的老手反而值得回头重看第五题的事前试验设计——很多项目最后失败不在模型精度而在方案阶段的抽样和对照逻辑就已经错了。2. 异常值识别进阶Grubbs 检验与五种检测法的选型逻辑2.1 异常值的统计定义与判别思路异常值Outlier指样本中数值明显偏离所属样本其余观测值的个别点数理统计里通常描述为“与平均值的偏差超过两倍标准差”。这个定义容易让人误以为 2σ 是通用硬阈值实际它只是基于正态近似的经验速判。真实项目里2σ 规则在样本量小、分布偏态时误判率很高右侧长尾分布会把正常高值打成异常值而重尾分布又会漏掉真正的离群点。所以笔试考 Grubbs 检验本质是考你是否理解“假设分布 检验统计量 临界值”这套统计推断框架而不是背一个阈值公式。2.2 五种检验法的比较与适用场景原题提到未知总体标准差 σ 时五种检验法的优劣次序为 t 检验法、格拉布斯检验法、峰度检验法、狄克逊检验法、偏度检验法。这个次序反映的是检验功效与稳健性的权衡。实际应用中我一般按下面这张表做选型检验方法核心思想适用场景主要局限t 检验法拉依达扩展用 t 分布修正小样本临界值样本近似正态、单侧离群受均值/标准差污染多重检验需校正Grubbs 检验最大偏差与标准差之比构造 G 统计量单变量数据集、总体近似正态一次只能识别一个异常点需迭代使用狄克逊检验Dixon极差比构造 Q 统计量样本量小n ≤ 30对多个异常点同时存在时不敏感偏度检验利用偏度方向捕捉单侧离群分布偏态明显的数据前提是分布形态已知峰度检验利用峰度捕捉尾部极端值尾部较厚的连续数据对中等程度离群点功效较弱从这张表能看出Grubbs 检验的核心假设是“数据集来自正态总体”它把异常值问题转化为一个假设检验问题原假设是所有数据来自同一正态总体备择假设是最大值或最小值是离群点。这比单纯画箱线图“肉眼找点”多了可量化的 P 值。但也要注意Grubbs 检验每次只能揪出一个离群点删掉后必须重新计算——因为残存的异常值会继续拉偏均值和标准差。2.3 用 Python 实现 Grubbs 检验并联动可视化Python 里没有直接叫grubbs的 SciPy 函数但用 t 分布临界值可以手写完整流程。下面这段代码输入一个连续型一维数组输出 G 统计量、临界值和判定结果import numpy as np from scipy import stats def grubbs_test(data, alpha0.05): x np.asarray(data, dtypefloat) n len(x) mean np.mean(x) std np.std(x, ddof1) # 计算 G 统计量|最大值或最小值 - 均值| / 标准差 G np.max(np.abs(x - mean)) / std # 临界值由 t 分布修正得到alpha 需除以 2n 做多重比较校正 t_crit stats.t.ppf(1 - alpha / (2 * n), n - 2) G_crit (n - 1) / np.sqrt(n) * np.sqrt( t_crit**2 / (n - 2 t_crit**2) ) return G, G_crit, G G_crit data [3.2, 3.1, 3.4, 3.0, 9.8, 3.3, 2.9] G, G_crit, flag grubbs_test(data) print(fG{G:.3f}, G_crit{G_crit:.3f}, 异常{flag})这段代码有两个关键参数需要解释。第一ddof1表示用样本标准差而不是总体标准差因为笔试场景里我们拿到的几乎都是样本无偏估计更稳。第二临界值公式里的alpha / (2 * n)是 Bonferroni 校正——对 n 个点做离群检测本质是多重比较不校正会放大误报。实际使用时如果数据里存在多个异常点应该每轮删除一个点后重新调用函数直到没有异常为止同时记录每轮删除的值方便后面写数据质量报告。配合可视化看更直观。用箱线图辅助预检是数据分析与可视化里的常见做法先画seaboxplot看分布形状如果箱子上下须之外的点不多再用 Grubbs 做定量确认。箱线图用的是 IQR 规则Q3 1.5×IQR不依赖正态假设和 Grubbs 检验正好互补——一个负责初步探查一个负责给出可报告的 P 值。2.4 异常值的处理决策删除、替换还是保留检出异常值只是第一步真正影响分析结果的是后续处理策略。我的经验是分三种情况。第一如果是数据录入错误比如年龄字段出现 999金额字段出现负数直接删除或修正并在数据质量报告里写明原因和比例。第二如果是真实业务极端值比如大促当天的销售额不要急着删先用 Grubbs 检验确认统计意义上的离群再结合业务判断这类点往往是高价值客户或真实峰值删除会低估业务波动。第三如果是建模场景可以考虑用中位数或 Winsorize 缩尾替换而不是直接剔除——树模型对异常值不敏感但线性回归和聚类对异常点非常敏感k-means 的均值更新会被极端点带偏。处理完后把原始值与处理后的值都留存方便复盘时追溯。3. k-means 到 K 中心点聚类笔试题的计算过程与 Python 复现3.1 聚类与分类的边界聚类分析Cluster Analysis把研究对象划分为相对同质的群组也叫分类分析或数值分类。笔试里最常见的考点是“聚类与分类的区别”——一句话概括分类的类别标签是预先知道的属于有监督学习聚类的类别是数据自身结构决定的属于无监督学习。很多候选人会答成“分类是按标签分、聚类是按特征分”这个表述不够准确关键差异在于划分前类别是否已知。聚类计算方法主要有五类层次方法、划分方法、基于密度的方法、基于网格的方法、基于模型的方法。前两种基于距离度量后三种分别针对不规则形状、高维网格、概率分布场景。3.2 k-means 的手算推导与收敛判断k-means 是划分方法里最常考的算法原题要求描述计算原理和步骤。标准流程是四步第一步从 n 个对象里任意选 k 个作为初始聚类中心第二步把每个剩余对象按距离分配给最近的聚类中心第三步重新计算每个聚类的均值作为新中心第四步重复第二、三步直到标准测度函数收敛一般用均方差MSE作为收敛判据。算法复杂度是 O(NKt)N 是数据量K 是类别数t 是迭代次数实际场景 K 远小于 Nt 也远小于 N所以它对大数据集相对可伸缩。下面用一个 5 个二维点、K2 的小例子手动推一遍K2初始中心选 A1(1,1) 和 A3(8,8)点坐标到 C1(1,1) 距离到 C2(8,8) 距离第一轮分配第二轮分配A1(1,1)0.09.9C1C1A2(1,2)1.08.6C1C1A3(8,8)9.90.0C2C2A4(8,9)10.61.0C2C2A5(9,8)10.61.0C2C2第一轮结束后 C1 的均值是 (1.0, 1.5)C2 的均值是 (8.33, 8.33)。用新中心重新计算距离A1、A2 仍然离 C1 近A3、A4、A5 仍然离 C2 近分配不再变化算法收敛。这个例子也暴露了 k-means 的两个固有缺点K 值必须事先给定但非常难以选定初始聚类中心的选择会直接影响最终结果不同初始化可能得到不同的局部最优解。笔试如果继续追问通常还会问“k-means 适合什么样的数据”——答案是适合密集的、类间区别明显的数据此时平方误差最小对环形、条状等非凸簇效果很差这时应该考虑 DBSCAN。3.3 用 scikit-learn 复现与 K 值选择用 scikit-learn 复现上面的过程非常直接但参数设置里有几个容易被忽略的细节import numpy as np from sklearn.cluster import KMeans from sklearn.preprocessing import StandardScaler X np.array([[1, 1], [1, 2], [8, 8], [8, 9], [9, 8]]) # 先标准化再聚类量纲不同会直接扭曲距离计算 X_scaled StandardScaler().fit_transform(X) km KMeans( n_clusters2, initk-means, # 用概率方式初始化避免随机中心导致的局部最优 n_init10, # 跑 10 次选 inertia 最小的结果 max_iter300, random_state42 ) km.fit(X_scaled) print(标签:, km.labels_) print(聚类中心:, km.cluster_centers_) print(MSE:, km.inertia_)这里initk-means是关键参数它的初始化策略是让初始中心彼此尽量远能显著降低随机初始化带来的结果波动。n_init10意味着算法独立跑 10 次保留 SSE簇内平方误差和最小的一次这是应对 k-means 对初始值敏感的标准做法。random_state固定随机种子保证别人能复现你的结果。inertia就是我们手算过程中的“标准测度函数”也就是各点到所属簇中心的距离平方和。K 值选择没有银弹但有两个可落地的辅助手段。一是肘部法Elbow Method对 K1 到 10 分别跑 k-means记录 inertia画折线图取“肘部”位置——inertia 下降速率明显变缓的拐点。二是轮廓系数Silhouette Coefficient计算每个样本与同簇样本的平均距离 a、与最近其他簇样本的平均距离 b系数为 (b-a)/max(a,b)取值在 [-1,1]越接近 1 说明簇内紧凑、簇间分离对不同 K 画轮廓系数均值曲线取最高点。实际业务里还要结合可解释性比如用户分群项目里 K4 分出的四个群各自有清晰的画像标签即使轮廓系数比 K5 低一点也值得选 K4。3.4 K 中心点算法与 k-means 的对比网易笔试题考察了 K 中心点聚类PAM它和 k-means 的核心差异在于“中心”的定义。k-means 用簇内均值作为中心K 中心点用簇内真正存在的某个样本作为中心medoid因此对异常值更稳健。PAM 的流程是先为每个簇随意选择一个代表对象把剩余对象按距离分配给最近的代表然后反复用非代表对象尝试替换代表对象判断替换能否改进聚类质量。判断标准里最经典的是四种情况——假设当前代表是 Oj尝试用非代表对象 O 替换 Oj考察任意非代表对象 p第一种p 当前属于 Oj如果 Oj 被 O 替换后 p 离 Oi 最近则 p 重新分配给 Oi第二种p 当前属于 Oj替换后 p 离 O 最近则 p 重新分配给 O第三种p 当前属于 Oi替换后 p 离 Oi 仍然最近则 p 分配不变第四种p 当前属于 Oi替换后 p 离 O 最近则 p 被重新分配给 O。四种情况下总代价下降替换才被接受。对比两者k-means 计算快、适合大规模数据但结果受异常值和初始中心影响大K 中心点更稳健但每次替换都要遍历所有非代表对象与所有代表对象组合复杂度通常到 O(K(N-K)²)大数据集上跑不动。所以大数据分析与挖掘实践中常见做法是先对数据做抽样或 Mini-Batch KMeans 粗聚类再对异常敏感的关键群体用 K 中心点细化。另外还有一个容易忽略的细节无论 k-means 还是 K 中心点都必须先做标准化否则量纲大的特征会主导距离计算这在高维用户特征聚类里是新手最容易踩的坑。4. SQL 取数与 B2C 销售归因首个 URL 的三种写法4.1 原答案的隐患在哪原题要求从表 AMember_ID、Log_time、URL中提取每个用户访问的第一个 URL形成新表 B给出的答案是CREATE TABLE B AS SELECT Member_ID, min(Log_time), URL FROM A GROUP BY Member_ID;这个写法表面看能跑通实际存在语义问题GROUP BY Member_ID后SELECT 里的URL并不在分组键中SQL 标准并不保证返回的是min(Log_time)对应那一行的 URL。某些数据库比如 MySQL 的 ONLY_FULL_GROUP_BY 未开启时会返回任意一行的值Hive 早期版本也有类似行为结果可能是用户第一条访问记录也可能是最后一条甚至是一个毫无规律的中间值。笔试现场写出这个答案暴露出的是对分组聚合语义理解不深——只知道用 MIN 取最早时间没意识到“和最早时间同行的其他字段”需要单独关联取回。4.2 窗口函数改写与等价子查询推荐优先用窗口函数语义直接、可读性强Hive、SparkSQL、MySQL 8.0 都支持CREATE TABLE B AS SELECT Member_ID, Log_time, URL FROM ( SELECT Member_ID, Log_time, URL, ROW_NUMBER() OVER ( PARTITION BY Member_ID ORDER BY Log_time ASC ) AS rn FROM A ) t WHERE rn 1;这段 SQL 的逻辑分三层。内层用ROW_NUMBER()按 Member_ID 分组PARTITION BY组内按 Log_time 升序编号最早一条的 rn1外层用WHERE rn 1过滤出每个用户的首条访问记录最后CREATE TABLE B AS把结果物化为新表。ROW_NUMBER()的作用是给组内每一行生成不重复的序号相比GROUP BY加MIN()的写法它的优势是能把“最早时间”和“最早时间对应的完整行”一次取出来不用二次关联。如果数据库版本不支持窗口函数退而求其次可以用等值关联的版本CREATE TABLE B AS SELECT a.Member_ID, a.Log_time, a.URL FROM A a JOIN ( SELECT Member_ID, MIN(Log_time) AS first_time FROM A GROUP BY Member_ID ) t ON a.Member_ID t.Member_ID AND a.Log_time t.first_time;子查询先计算每个用户的最早时间再与原表按用户和时间做等值连接。这个写法的问题在于如果同一个用户在同一个时间戳有多条访问记录连接后会产生重复行需要再套一层 DISTINCT 或 ROW_NUMBER 去重。所以在生产环境里我会优先使用窗口函数版本只有在必须兼容 MySQL 5.7 以下的老系统时才用子查询关联。还有一点容易被忽略Log_time字段如果是字符串类型的日期比如2024-05-20 10:30直接 ORDER BY 是按字典序排的格式不统一时会出错建议先CAST(Log_time AS TIMESTAMP)或统一格式再排序。4.3 从 SQL 取数到销售数据归因分析SQL 是取数基本功但笔试第四题明确告诉你光会取数不够数据解读能力才是区分度所在。原题给的是某 B2C 电子商务网站一周销售数据用户群是办公室女性销售额集中在 5 类产品上。假设一周数据如下星期销售额万元周一12.8周二13.5周三12.1周四13.2周五11.6周六6.4周日5.8从数据能直观看到的结论是周末销售额明显偏低。但归因不能停在“周末没人买”要拆成两个视角。消费者视角办公室女性是核心用户群周一到周五的购买场景通常与工作间隙、午休浏览相关周末脱离办公场景后触达减少购买欲望下降。产品视角这 5 类产品本身是否具备周末消费属性如果品类里没有适合周末场景的商品比如零食补货、居家用品那么周末流量即使来了也转化不了。运营改进计划也对应分两条线一是引导提醒通过邮件、短信、App Push 在周五下午或周六上午推送“周末备货”主题把购买场景从办公室迁移到家庭二是促销拉动周末针对 5 类主打产品做限时折扣或满减人为制造关注点。这里还有一个电商业务数据分析中容易犯的错误拿到一周数据就直接归因没有先剥离周期性。正确做法是先看这个周有没有节假日、有没有大促活动日、上周同期数据如何。比如如果周一本身是 618 大促后的回落日销售额低就不是“周一效应”而是“大促透支效应”。更严谨的方案是至少取 4~8 周数据按星期几做同环比再用异常值检测把促销日标记出来最后才做归因。数据解读的价值不在于描述“周末低”这个现象而在于区分哪些是可干预因素促销、提醒、选品哪些是结构性因素目标用户的消费习惯运营动作只对可干预因素有效。5. 事前试验与 AB 测试分层抽样和两独立样本 t 检验的完整落法5.1 试验方案要回答什么问题第五题的场景是公司针对 A、B、C 三类客户提出统一改进计划目标是提升客户周消费次数要制定事前试验方案来支持决策。第一问“试验需要为决策提供什么样的信息”很多人的回答是“测试有没有效果”这不严谨。正确的拆解是试验要能证明该改进计划对 A、B、C 三类客户的周消费次数有“统计显著”的提升——不仅要说有效果还要排除随机波动造成误判的可能。同时还要回答效果量的大小提升了 0.5 次和 0.05 次对业务决策的意义完全不同。所以事前试验方案需要包含三个要素抽样方法、采集的数据指标、统计检验方法三者缺一不可。5.2 分层比例抽样的计算抽样方法选分层比例抽样是合理的因为 A、B、C 三类客户在总体中的占比不同简单随机抽样可能让样本中某类客户数量过少导致该类客户的检验功效不足。分层比例抽样的做法是先按客户类别分成三层每层按其在总体中的比例分配样本量# 假设总体中三类客户数量 pop {A: 10000, B: 15000, C: 25000} total sum(pop.values()) # 50000 sample_size_total 5000 # 预定总样本量 allocated { k: int(round(sample_size_total * v / total)) for k, v in pop.items() } print(allocated) # {A: 1000, B: 1500, C: 2500}这里sample_size_total * v / total就是按每类客户占比分配样本A 类占 20% 就抽 1000 人C 类占 50% 抽 2500 人保证各类客户的样本结构与总体一致。需要采集的数据指标有两类客户类别用于分层标识前后周消费次数用于检验效果改进前的周消费次数作为基线改进后的周消费次数作为度量。注意要把两次数据都采全有缺失值的样本要提前剔除否则会出现“只采到改进前数据但客户流失”的不完整配对影响检验有效性。5.3 两独立样本 t 检验的前提与计算题目给出的统计方法是针对 A、B、C 三类客户分别做改进前后周消费次数的两独立样本 t 检验。用 Python 实现核心是scipy.stats.ttest_indfrom scipy import stats # 假设改进前抽样 120 人的周消费次数 before [2.8, 3.1, 2.9, 3.0, 2.7, 3.2, 2.6, 3.3] # 示例数据 # 假设改进计划落地后独立抽样的 120 人周消费次数 after [3.4, 3.6, 3.1, 3.8, 3.5, 3.2, 4.0, 3.7] t_stat, p_value stats.ttest_ind(after, before, equal_varTrue) print(ft{t_stat:.3f}, p{p_value:.4f}) if p_value 0.05: print(差异显著改进计划有效) else: print(差异不显著无法判断有效)ttest_ind的两个关键参数是equal_varTrue对应经典的学生 t 检验假设两组方差齐性如果两组标准差差异较大应设equal_varFalse使用 Welch 修正这是更稳健的做法即使方差不齐也能用。两个样本是独立抽的即改进前样本与改进后样本来自不同客户而不是同一批客户——如果是同一批人测量两次那就应该用ttest_rel配对 t 检验功效更高。题目问的是“两独立样本 t 检验”按独立分组设计即可但实际业务中如果条件允许优先用同一批客户的前后对比这样能剔除个体差异同样的样本量更容易得到显著结果。5.4 样本量估计与常见坑事前试验最容易翻车的是样本量不够跑完两周发现 P 值在 0.05 到 0.10 之间不上不下。样本量大小取决于四个输入显著性水平 α通常 0.05、检验功效 1-β通常 0.80、两组均值差 δ、总体标准差 σ。两独立样本均值检验的样本量公式是 n 2σ²(z_{α/2} z_β)² / δ²每组需要的样本量from scipy import stats alpha 0.05 beta 0.20 z_alpha2 stats.norm.ppf(1 - alpha / 2) z_beta stats.norm.ppf(1 - beta) sigma 2.0 # 周消费次数的标准差从历史数据估计 delta 0.5 # 希望检出的最小效果提升 0.5 次/周 n int(2 * sigma**2 * (z_alpha2 z_beta)**2 / delta**2) 1 print(f每组至少需要 {n} 人)这里 σ 用历史数据算比如过去一个月客户周消费次数的样本标准差。z 值来自标准正态分布分位数stats.norm.ppf(0.975)约为 1.96stats.norm.ppf(0.80)约为 0.84。把公式跑出来后如果每组需要 251 人而分层抽样每层只分到 200 人就要调整总样本量或降低预期效果 δ——这是方案评审阶段应该完成的容量计算而不是试验结束后再解释。试验设计还有三个常见坑。第一随机化单元选错如果改进计划是针对客服服务的同一客服服务的客户不满足独立性应该按客服分组而不是按客户随机分组。第二AA 测试缺失上线正式实验前先跑一段没有真实差别的 AA 测试验证两组基线指标一致避免分组偏差导致误判。第三多重比较问题A、B、C 三类客户分别做检验做了 3 次 t 检验整体犯第一类错误的概率会大于 0.05可用 Bonferroni 校正把 α 调整为 0.05/3 ≈ 0.0167。这个细节如果能在笔试或面试里主动提出来通常能明显加分。6. 编程与算法笔试通用解法归并排序、哈希冲突与 FMM 分词实现百度、网易、腾讯的笔试把范围从统计拓展到工程算法其中三个考点最具代表性排序算法的稳定性、哈希冲突的解决方案、中文分词的前向最大匹配算法。先看排序稳定性。快速排序不稳定是因为它基于交换相等元素的相对次序在分区时可能被打乱归并排序稳定是因为合并两个有序序列时遇到相等元素先取左半部分的元素。笔试若让你手写归并排序用链表实现能同时考察指针操作struct Node { int v; Node *next; Node(int x) : v(x), next(nullptr) {} }; Node* merge(Node* a, Node* b) { Node dummy(0), *cur dummy; while (a b) { if (a-v b-v) { cur-next a; a a-next; } else { cur-next b; b b-next; } cur cur-next; } cur-next a ? a : b; return dummy.next; }哈希冲突的解法里笔试常要求写两种并比较优劣最基础且实用的方案是链地址法每个桶挂一条链表冲突元素追加到链表尾部优点是删除操作方便、扩容简单另一种是开放定址法冲突时按线性探测或二次探测找下一个空位优点是空间连续、缓存命中高但删除操作复杂且表快满时性能急剧退化。至于“10 亿 URL、平均 20 字节、8G 内存统计频次”这类题思路固定先按 URL 做哈希分片到多个小文件让每个文件能在内存中建 HashMap 统计频次再用大小为 K 的小顶堆取 TopK对应每行一个 URL 去重的场景。最后是 FMM 分词网易和百度都考过。前向最大匹配的核心思路并不复杂从句子当前位置开始取最长为 max_len 的子串查词典命中就切分并前移不命中就缩短窗口继续查直到单字兜底。关键在词典的struct dictnote设计用 Trie 树比哈希表更合适因为匹配过程本身就是逐字下探的struct DictNode { unordered_mapwchar_t, DictNode* children; bool is_end; string word; // 命中时的完整词 };匹配时从根节点出发沿句子的字序列在children中逐层查找is_end标记当前位置是否为完整词。接口设计上题目建议用int FMM(vectorwchar_t sentence, DictNode* root, vectorpairint,int* results)这类输出位置对的签名比直接返回字符串更灵活——下游不管是做词性标注还是品牌检索拿到位置索引都能自由切分。扩展“查找包含手机品牌的网页”时只需把品牌词表如 iPhone、诺基亚也插入同一棵 Trie匹配到品牌词时额外标记一个品牌 ID再从网页文本里扫描品牌词出现的所有位置即可实现泛化的品牌发现。FMM 的短板是切分歧义正向最大匹配对“南京市长江大桥”这类歧义句容易切错改进方向是引入逆向最大匹配RMM并比较两种结果的分词数量取更少的一种——这就是双向最大匹配的雏形也是笔试后续追问时值得展开的技术点。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价