资讯动态

Joyful Pandas 补充习题全解析:7 道 pandas 实战题的向量化思维与解法思路

发布时间:2026/10/13 6:29:31 来源:尧图企业网站定制
文档教程数据分析【免费下载链接】joyful-pandaspandas中文教程项目地址https://gitcode.com/gh_mirrors/jo/joyful-pandas点击查看免费下载本文基于 Joyful Pandaspandas 中文教程仓库中的 source/补充习题.rst 整理而成系统拆解 Ex1Ex7 共 7 道综合习题的题干设定、涉及知识点、可用数据文件与参考答案思路。读者读完本文后将能理解如何用 NumPy 向量化运算替代显式循环、如何用 pandas 的 groupby / merge / nlargest / pivot 等核心 API 完成成绩统计、审核流程分析、日志解析等真实场景任务并掌握尽可能不使用 for/while 循环这一 pandas 式编程要义。补充习题是 Joyful Pandas 教程十三个章节之外的一套综合实战题覆盖 NumPy 向量化、索引、分组、变形、连接、缺失数据、文本数据、分类数据与时序数据等全部核心主题。整套习题开头有一条硬性要求在补充习题中尽可能不要使用任何 for 或 while 循环。这条约束与教程正文反复强调的pandasic 代码要义一脉相承pandas 的绝大多数操作都基于 C 层向量化实现显式 Python 循环不仅代码冗长性能也往往相差几个数量级。下面逐题展开。Ex1NumPy 的向量化运算Ex1 是整套习题的开胃菜用 4 个小题训练纯 NumPy 层面的向量化思维。涉及的关键函数包括np.diff、np.nonzero、np.cumsum、np.argsort以及广播机制。1. 缺失的最小正整数给定一个正整数列表找出缺失的最小正整数。题干给出的三个测试用例 arr np.array([2,3,4]) get_miss(arr) 1 arr np.array([6,3,5,1,2]) get_miss(arr) 4 arr np.array([5,2,1,3,4]) get_miss(arr) 6参考思路先对数组去重并排序用np.diff检查相邻元素的差若差值大于 1则缺失值位于这两个元素之间若整个数组是连续整数则答案是最小值减 1数组起始不为 1 时或最大值加 1。该思路与 参考答案.rst 第一章 Ex5 连续整数的最大长度中使用的技巧同源——那里用np.diff(np.nonzero(np.r_[1, np.diff(x)!1, 1])).max()找出最长连续段长度可见np.diffnp.nonzero是处理连续性问题的标准组合。2. 生成阶梯状二维数组get_res(n)函数输入正整数 n按如下规则构造二维数组第 1 行填入 1 个 1第 2 行在上一行填入位置的下一列连续填入 2 个 2依此类推。n4 时的期望输出array([[1., 0., 0., 0., 0., 0., 0., 0., 0., 0.], [0., 2., 2., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 3., 3., 3., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 4., 4., 4., 4.]])参考思路第 i 行的起始列是前 i-1 行累计填入的个数即cumsum思想。先构造一个足够宽的全零矩阵如 10 列再通过行索引与列偏移的组合一次性写入每行连续的一段。整个构造可以用np.arange与广播完成不需要逐行循环。3. 随机游走极限估计点 A 从数轴原点出发做 n 步简单随机游走每步等概率向左或向右移动 1可以证明$$\lim_{n\rightarrow\infty}\frac{\mathbb{E}|S_n|}{\sqrt{n}}\sqrt{\frac{2}{\pi}}$$题目要求取 n5000 进行 1000 次试验每次试验用 $\frac{1}{100}\sum_{k1}^{100}|S_n|$ 代替 $\mathbb{E}|S_n|$计算 1000 个估计值的均值、0.05 分位数与 0.95 分位数。参考思路把 1000 次试验、每次 100 条路径、每条路径 5000 步的随机游走整体构造成一个三维数组np.random.choice([-1, 1], size(1000, 100, 5000))沿最后一维累加得到每条路径的终点取绝对值后沿路径维求均值最后一次性得到 1000 个估计值。构造三维数组并利用sum/mean的axis参数是完全避免两层 for 循环的关键。随后用np.mean、np.quantile或np.percentile即可计算所需统计量。4. 点云相关矩阵 S二维平面上有 n 个点坐标node_xy与 k 维特征node_fea点 a、b 的相关系数定义为$$S_{ab} \frac{\sigma_{ab}}{2} \frac{\lambda_{ab}}{2}$$其中 $\sigma_{ab}$ 是特征向量 A、B 的余弦相似度$$\sigma_{ab} \frac{\sum_{i1}^kA_iB_i}{\sqrt{\sum_{i1}^kA^2_i}\sqrt{\sum_{i1}^kB^2_i}}$$$\lambda_{ab}$ 基于二维平面距离排名把到点 a 的距离排序最近的点点 a 自身排名为 1点 b 排名记为 $r^{(a)}_b$则$$\lambda_{ab} 1 - \frac{2\times (r^{(a)}_b-1)}{n-1}$$题干明确提示使用np.argsort()。参考思路分三步欧氏距离矩阵利用广播计算d np.sqrt(((node_xy[:, None, :] - node_xy[None, :, :])**2).sum(-1))得到 $n\times n$ 的距离矩阵排名矩阵对距离矩阵沿 axis1 做argsort再对结果做一次argsort即可把距离转换为排名每行排名从 0 开始对角线恰好为 0即自身排名第 1。这一argsort 后再 argsort的写法是把排序结果映射回原始下标的标准技巧特征余弦相似度sigma (node_fea node_fea.T) / np.outer(np.sqrt((node_fea**2).sum(1)), np.sqrt((node_fea**2).sum(1)))此时 $\sigma$ 对角线恒为 1向量与自身的余弦相似度。最后S (sigma lam) / 2即可得到完整的 $n\times n$ 相关矩阵。Ex2统计学生的成绩情况数据说明data/supplement/ex2 目录存放了某校高三第一学期的成绩数据包含 16 次周测成绩、期中考试成绩和期末考试成绩共 18 个 CSV 文件实测每个文件 897 行即 896 名学生。各文件列结构一致班级,姓名,选科,语文,数学,英语,科目 1,吴刚,地理,93,95,82,69所有表中相同的行表示同一位同学可用班级 姓名作为连接键选科共有 6 种取值物理、化学、生物、历史、地理、政治周测 1 中人数分布约为化学 246、生物 166、历史 132、物理 131、地理 127、政治 94科目列存放该生选科考试的成绩无缺失值。典型读取方式df pd.read_csv(data/supplement/ex2/第1次周测成绩.csv) df.head()题目拆解Q1该校高三年级中是否存在姓名相同的学生直接用df[姓名].duplicated().any()判断。注意同名判断需要限定在同一班级内不同班级同名是正常的严格做法是按[班级, 姓名]去重后比较数量。Q2第一次周测中每个班级选修物理或化学同学的语数英总分平均值哪个班级最高q df[df[选科].isin([物理, 化学])] res q.groupby(班级)[[语文, 数学, 英语]].sum().mean(axis1) res.idxmax()先布尔筛选出物理/化学选科的学生再按班级分组求和最后按行求三科均值。实测示例数据中最高的是4 班。该题考察isin布尔筛选与groupby分组聚合的组合。Q3总评前十总评 各次考试总分的加权平均值周测权重 50%16 次等权每次 3.125%、期中 20%、期末 30%。参考流程用pd.concat把 16 次周测按同一键纵向拼接或用pd.concat(..., keysrange(16))保留周次信息每次考试的总分 语文 数学 英语 科目周测部分先求每次总分再按学生求均值再与期中、期末总分按 0.5 / 0.2 / 0.3 加权求和用nlargest(10, 总分)取出前十。nlargest是 pandas 提供的无需排序再取头 N 个的高效 API本题明确要求结合 nlargest 函数。实测示例数据中年级总评前十的第一名是 8 班的王想总分约 427.2。Q418 班文理科期末总分前 5物化生为理科、政史地为文科统计各班期末总分前 5输出格式要求如王大锤历史102即姓名选科选科分数拼接字符串。参考思路读取期末成绩按选科分出文理两组groupby([班级, 文理])后对总分做nlargest(5)或对每组sort_values后取head(5)用apply/字符串拼接把姓名选科科目分格式组装成 DataFrame 列最终拼出 16 列的宽表8 个班 × 文/理。Q5成绩稳定性分析稳定性定义为每次考试在全年级相同选科学生中的总分排名标准差。参考流程把 18 次考试成绩表纵向合并构造 (学生, 选科, 考试) 维度的长表对每次考试、每个选科分别做rank(ascendingFalse)得到该生该次考试在年级同选科中的总分排名按学生计算排名标准差再按班级×选科聚合求均值最终得到行索引为班级111、列索引为 6 个选科的 11×6 表格pd.DataFrame( np.random.rand(11, 6), indexpd.Index(range(1, 12), name班级), columnspd.Index([物理, 化学, 生物, 历史, 地理, 政治], name选科), )本题综合考察长表构造、rank排名、多重分组与groupby聚合是分组章节知识点参见 source/Content/ch4.rst的完整应用。Ex3统计商品的审核情况数据说明data/supplement/ex3 下有两张表商品信息.csv约 26832 个商品列为ID号, 识别码, 类别类别取值如 T1、Q1、S1、S2申请与审核记录.csv约 147500 条审核记录列为ID号, 申请人, 申请时间, 审核人, 审核时间, 结果结果仅通过与未通过两种。审核流程为申请人发起申请 → 审核人审核 → 通过或未通过未通过可再由另一位申请人重新申请直到通过为止。同一商品最多可能有 10 次审核记录。商品 ID 以 ID 000001 形式存在申请人/审核人字段是含特殊字符的字符串如\#3((52\{、3}04}%75读取后需注意字符串清洗。df_info pd.read_csv(data/supplement/ex3/商品信息.csv) df_record pd.read_csv(data/supplement/ex3/申请与审核记录.csv)题目拆解Q1有多少商品最终通过审核passed df_record[df_record[结果] 通过][ID号].nunique()由于每个商品最终要么通过要么未通过直接用最后一条记录或是否存在通过记录判断即可。注意一个商品有多条记录统计的是商品数而非记录数。Q2各类别商品的通过率先把商品信息与审核记录按ID号连接再按类别分组通过率 该类别最终通过的商品数 / 该类别商品总数。这里需要每个商品取最终状态因为审核流程保证未通过可以继续申请所以每个商品的最新一条记录的结果就是其最终状态可用sort_valuesgroupby(ID号).tail(1)或groupby(ID号).last()实现。Q3类别 T1 且最终通过的商品的平均审核次数先取最终通过的 T1 类商品再统计这些商品在审核记录表中出现的次数即审核次数最后求平均。审核次数可由groupby(ID号)[结果].count()得到。Q4是否存在商品在上一次审核未完成时就提交了下一次审核申请把审核记录按ID号、申请时间排序后用groupby(ID号)[申请时间].diff()计算同一商品相邻两次申请的时间间隔检查是否存在前一次审核时间晚于后一次申请时间的记录d df_record.sort_values([ID号, 申请时间]) # 上一次审核时间与本次申请时间比较严格判定是对每个商品的记录按申请时间排序判断申请时间[i1] 审核时间[i]是否成立。该题考察分组内自比较groupbyshift/diff是标准工具。Q5审核通过商品的第一位申请人与最后一位审核人pd.DataFrame({ ID号: [ID 000001] [...]*3, 类别: [T1] [...]*3, 申请人: [\#3((52\{] [...]*3, 审核人: [3}04}%75] [...]*3, }, index[1, 2, 3, ...])参考思路对最终通过的每个商品按其全部审核记录取第一条最早的申请人与最后一条最新的审核人再与商品信息连接补上类别列。题目提示groupby 对象上也定义了 head 和 tail 方法——即df_grouped.head(1)/df_grouped.tail(1)可取每个组的最前/最后若干行这是本题的核心技巧。同样以sort_values保证组内顺序为前提。Ex4删除同样的行给定两张表在 df1 中剔除在 df2 中出现过的行df1 pd.DataFrame({A: [3,2,2,3,1,3], B: [2,1,1,3,6,2], C: [1,2,2,7,7,1], D: [5,6,6,1,2,5]}) df2 pd.DataFrame({A: [2,3,1], B: [1,9,6], C: [2,7,7], D: [6,1,2]})期望结果保留 df1 中三行第 0、3、5 行pd.DataFrame({A: [3,3,3], B: [2,3,2], C: [1,7,1], D: [5,1,5]})参考思路利用pd.merge的indicatorTrue参数做外连接 来源标记再筛选_merge left_only的行res df1.merge(df2, howleft, indicatorTrue) res res[res[_merge] left_only].drop(columns_merge)该题的另一个常用解法是把两表拼接后drop_duplicates(keepFalse)但由于 df1 自身存在重复行如第 1、2 行同为[2,1,2,6]用merge indicator更稳妥。本题对应连接章节的关系连接知识点参见 source/Content/ch6.rst实测结果与期望输出完全一致。Ex5统计每个学区的开课数量数据说明data/supplement/ex5/school_course.csv 共 1921 行列为Area, School, CourseArea,School,Course area_1,school_99,course_90 area_2,school_32,course_20 area_3,school_64,course_38 area_1,school_231,course_9 course_40 course_100某城市共 4 个学区area_1area_4共 367 所互不重名的学校一条记录表示该学校开设的一门或多门课程同一学校的不同记录之间课程可能重复实测约 1274 行 Course 字段包含空格即一条记录含多门课程课程以空格分隔课程种类共 100 门编号course_1course_100。解题要点要求统计每个学区开设每门课程的学校数量学校去重输出 100 行 × 4 列的宽表res pd.DataFrame( 0, index[course_%d % (i1) for i in range(100)], columns[area_%d % (i1) for i in range(4)] )参考思路分三步拆课程把Course字段按空格拆分——最自然的方式是df[Course].str.split()配合explode()将一行多条课程拆成多行去重按[Area, School, Course]去重避免同一学校多记录导致的重复计数统计pd.crosstab(df[Course], df[Area])得到课程×学区计数表再按 100 门课程全集reindex补齐未开设的课程为 0。本题集中考察str.split、explode把列表展开为多行的变形操作、drop_duplicates与crosstab/pivot_table的组合使用是变形 分类 分组的综合应用题。Ex6捕获非零的行列索引给定如下数据框返回所有非零单元格对应的列-行组合构成的多级索引df pd.DataFrame( [[0,5,0], [2,1,0], [0,0,6], [0,9,0]], indexlist(ABCD), columnslist(XYZ)) df期望结果注意顺序按列优先res pd.Index([ (X, B), (Y, A), (Y, B), (Y, D), (Z, C)])参考思路核心是把二维布尔掩码摊平成一维后与列×行的笛卡尔积索引对齐idx pd.MultiIndex.from_product([df.columns, df.index]) res idx[df.to_numpy().ravel() ! 0]df.to_numpy().ravel()把 4×3 矩阵按行优先展开顺序恰好与MultiIndex.from_product([columns, index])的元素一一对应布尔索引一次性选出非零位置对应的(列, 行)组合。本题考察MultiIndex.from_product构造笛卡尔积多级索引、ravel展平以及布尔索引三者的配合属于索引章节中多层索引构造的应用参见 source/Content/ch3.rst。Ex7分析集群日志Ex7 是整套补充习题中数据量最大、综合性最强的一道考察文本解析、脏数据清洗、连接、透视与区间合并等多项能力。数据说明data/supplement/ex7/logs.txt 共约 35 万行实测 350668 行是某分布式文件集群 2022-09-27 一天的文件传输日志。集群共 134 台服务器分布于五个机房R0 机房 23 台、R1 机房 16 台、R2 机房 47 台、R3 机房 30 台、R4 机房 18 台每机房服务器编号从 001 开始。单行日志格式[PUSH|2022-09-27 07:08:12] Cluster#R1#007 | tgfuHOAjDJ | 4.41 GB [SAVE|2022-09-27 08:17:00] Cluster#R0#019 | neLAGbGkvd | 7.99 GB字段含义方括号内为操作类型与操作时间PUSH表示向其他服务器发出文件SAVE表示接收其他服务器文件Cluster#R4#014表示 R4 机房第 14 号机器十位字符串为传输文件的唯一标识file_id末尾为文件大小带单位如4.41 GB、91.64 MB若 SAVE 机器接收了某文件则必然存在另一台机器 PUSH 该文件的记录若同一对 PUSH/SAVE 的文件大小不一致则该次传输处于未完成状态Unfinished。日志读取示例with open(data/supplement/ex7/logs.txt, r) as f: for i, txt in enumerate(f.readlines()): if i 5: break print(txt.strip())Q1解析日志并清洗脏数据要求用高效方法提取信息清洗脏数据时间格式错误、无效数字输出如下格式push_time按时间排序pd.DataFrame({ file_id: [wfjqoIDhsD, QigjDSEGje, ...], file_size: [6.35, 149.23, ...], save_size: [6.32, np.nan, ...], # np.nan 表示没收到 push_from: [A3-007, A0-017, ...], push_to: [A2-012, np.nan, ...], # np.nan 表示没收到 push_time: pd.to_datetime([20220927 01:03:55, 20220927 01:03:58, pd.NaT]), save_time: pd.to_datetime([20220927 01:03:57, pd.NaT, pd.NaT]), }, index[0, 1, ...])参考思路正则解析用str.extract一次性拆出操作类型、时间、机房、机器号、file_id、大小与单位pat r\[(PUSH|SAVE)\|([0-9\- :])\]\sCluster#(R\d)#(\d)\s\|\s(\w)\s\|\s([\d.])\s(\w)单位统一把GB/MB统一换算为统一单位如 MB 或 GB将无效数字清洗为 NaN时间清洗把时间字符串用pd.to_datetime转为时间戳非法时间清洗为pd.NaT宽表拼接题目提示本质上是把两个一一对应的文件信息进行连接——先分别构造 PUSH 表与 SAVE 表再以file_id为键做outer join因为存在只有 PUSH 没有 SAVE 的 Missed 文件得到每条文件的一对推送/接收信息。这正是连接章节中方向连接的应用参见 source/Content/ch6.rst。Q2机房间平均传输速度对传输完成的文件PUSH 与 SAVE 大小一致按发送机房→接收机房分组计算平均速度MB/s输出 5×5 矩阵pd.DataFrame( np.random.rand(25).reshape(5, -1), index[R%d % i for i in range(5)], columns[R%d % i for i in range(5)], )参考思路速度 文件大小 / 传输耗时save_time - push_time先构造push_from 机房 → push_to 机房两列再用groupby([机房_from, 机房_to])[速度].mean()聚合最后unstack成矩阵。可进一步验证题干给出的猜想矩阵对角线同机房内传输均值是否高于非对角线跨机房传输这正是分组聚合 透视unstack/pivot_table的综合应用。Q3各机房传输状态比例定义文件传输状态Finished文件大小等于接收大小传输成功Missed日志中只有单条记录只有 PUSH 没有 SAVEUnfinished文件大小不等于接收大小按传输比例接收/发出超过 90% 与超过 50% 分为三档Unfinished-Almost、Unfinished-Fair、Unfinished-Bad。输出按状态排序Finished Unfinished-Almost Unfinished-Fair Unfinished-Bad Missed、再按机房号排序的比例表pd.DataFrame({ Status: [Finished]*3 [...] [Missed], Room: [R0, R1, R2, ..., R5], Ratio: [0.1, 0.15, 0.1, ..., 0.05], }, index[0, 1, 2, ..., 24])参考思路基于 Q1 拼接好的宽表用np.select或pd.cut按比例区间打标签再用groupby([Status, 机房]).size() / 总数计算比例最后用Categorical类型给 Status 设定有序类别后sort_values。有序类别排序正是分类数据章节source/Content/ch9.rst中cat.reorder_categories(orderedTrue)的典型应用场景。Q4按小时统计大文件收发差大文件指大小超过 800M 的文件。要求按小时计算每个机房发送的大文件数 − 接收的大文件数结果行索引为时间小时列索引为机房。参考思路分别对 PUSH 与 SAVE 记录筛选大文件groupby([小时, 机房]).count()后做差或pivot_table后相减自然衔接 Q1 的解析结果与dt.floor(H)/resample时间分组。Q5按小时统计每台机器的空闲率对某一台机器空闲时间指其既没有处于发送任务中又没有处于接收任务中的时间每小时的空闲率 该小时区间内空闲时间占比。输出行索引为时间、列索引为机器。题目给出了两条外部提示本题涉及到了区间合并问题pandas 的区间索引没有定义类似于 merge intervals 的函数请阅读 Stack Overflow 上Efficient merge overlapping intervals in same pandas dataframe with start and finish的讨论来思考如何实现。参考思路把每台机器的每条 PUSH/SAVE 记录视为一个忙碌区间[start, end]先按机器对区间做合并merge intervals得到互不重叠的忙碌区间集合再用合并后的总忙碌时长反推空闲时长按小时切分pd.interval_range或cut计算每小时的空闲占比。核心难点在于pandas 的IntervalIndex只提供区间构造与索引能力并未内置区间合并函数需要自行用排序 贪心扫描实现这是本题对时序数据 区间运算能力的最高阶考查。总结补充习题的技术地图习题主题核心 API / 技巧关联章节Ex1NumPy 向量化np.diff、np.argsort、广播、三维数组聚合第一章 预备知识Ex2学生成绩统计groupby、nlargest、rank、加权聚合第二章/第四章Ex3商品审核分析groupby.head/tail、sort_values、外连接第四章/第六章Ex4删除重复行merge(indicatorTrue)第六章 连接Ex5学区开课统计str.split、explode、crosstab第五章/第八章Ex6非零索引捕获MultiIndex.from_product、ravel、布尔索引第三章 索引Ex7集群日志分析正则解析、outer join、区间合并第六章/第十章整套补充习题的练习数据均位于仓库 data/supplement 目录下配套代码环境与正文章节保持一致import numpy as np; import pandas as pd; import matplotlib.pyplot as plt。各章正文与章末习题的参考实现可对照 参考答案.rst 学习。建议读者在动手时严格遵循题首要求尽可能不要使用任何 for 或 while 循环——当你发现自己忍不住要写循环时停下来思考对应的 pandas/NumPy 向量化写法这正是 Joyful Pandas 训练的核心目标。赞分享文档教程数据分析【免费下载链接】joyful-pandaspandas中文教程项目地址https://gitcode.com/gh_mirrors/jo/joyful-pandas点击查看免费下载相关推荐Joyful Pandas 综合实战从 NumPy 向量化到集群日志分析的 7 道补充习题全解指南Joyful Pandas 综合实战从 NumPy 向量化到集群日志分析的 7 道补充习题全解指南 导读 本文以 Datawhale《Joyful Pand数据分析数据科学教程joyful-pandas 全十章练习参考答案精讲从 NumPy 向量化到 pandas 高阶数据操作joyful pandas 全十章练习参考答案精讲从 NumPy 向量化到 pandas 高阶数据操作 《joyful pandas》是一份以实战练习驱动的数据分析数据科学教程Joyful-Pandas以习题驱动的 pandas 中文教程与配套纸质书资源体系Joyful Pandas以习题驱动的 pandas 中文教程与配套纸质书资源体系 本文以 Datawhale 开源仓库 joyful pandas http数据分析数据科学教程上一篇三大技术突破OpenCore Legacy Patcher如何让旧Mac重获新生下一篇从零到一Embodied-AI-Guide具身智能完整学习路线图与实战指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

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

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

免费获取报价 →
↑