补充习题#

In [1]: import numpy as np

In [2]: import pandas as pd

In [3]: import matplotlib.pyplot as plt

注意

在补充习题中尽可能不要使用任何for或while循环。

Ex1:NumPy的向量化运算#

  • 给定一个正整数列表,请找出缺失的最小正整数。

>>> 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
  • 设计一个生成二维NumPy数组的函数get_res(),其输入为正整数n,返回的数组构造方式如下:第1行填入1个1,第2行在上一行填入位置的下一列连续填入2个2,第3行在第二行最后一个填入位置的下一列连续填入3个3,…,第n行在第n-1行最后一个填入位置的下一列连续填入n个n。

>>> n = 4
>>> get_res(n)
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.]])
  • \(A\) 初始位置在数轴原点处,现进行n步简单随机游走,即每一步以等概率向左或向右移动长度为1的距离,记该点最终的位置为 \(S_n\) ,则可以证明

\[\lim_{n\rightarrow+\infty}\frac{\mathbb{E}|S_n|}{\sqrt{n}}=\sqrt{\frac{2}{\pi}}\]

现取n为5000进行1000次试验,且每次试验中用 \(\frac{1}{100}\sum_{k=1}^{100}|S_n|\) 来代替 \(\mathbb{E}|S_n|\) 。此时可以计算得到1000个 \(\lim_{n\rightarrow+\infty}\frac{\mathbb{E}|S_n|}{\sqrt{n}}-\sqrt{\frac{2}{\pi}}\) 的估计值,请计算这些估计值的均值、0.05分位数和0.95分位数。

  • 在二维平面上有n个点,每个点具有k维特征,点的坐标数据记录在node_xy中,点的特征数据记录在node_fea中。现要计算所有点的相关矩阵 \(S\) ,点a和点b的相关系数定义如下

\[S_{ab} = \frac{\sigma_{ab}}{2} + \frac{\lambda_{ab}}{2}\]

其中,若记点a特征为 \(A\) ,点b特征为 \(B\) ,则有

\[\sigma_{ab} = \frac{\sum_{i=1}^kA_iB_i}{\sqrt{\sum_{i=1}^kA^2_i}\sqrt{\sum_{i=1}^kB^2_i}}\]

对于点a而言,将所有点到点a的二维平面距离进行排序,从而得到每个点到点a的距离排名,距离最近(排名为1)的点是点a自身,记点b的排名为 \(r^{(a)}_b\) ,则定义

\[\lambda_{ab} = 1 - \frac{2\times (r^{(a)}_b-1)}{n-1}\]

请对于给定的node_xy和node_fea计算相关矩阵 \(S\) 。(提示:使用np.argsort())

>>> n, k = 1000, 10
>>> node_xy = np.random.rand(n, 2)
>>> node_fea = np.random.rand(n, k)
>>> get_S(node_xy, node_fea)

Ex2:统计学生的成绩情况#

在data/supplement/ex2目录下存放了某校高三第一学期的学生成绩情况,包含16次周测成绩、期中考试成绩和期末考试成绩,科目一栏的成绩表示学生选课的成绩。所有的表中,相同的行表示的是同一位同学。请完成以下练习:

In [4]: df = pd.read_csv('data/supplement/ex2/第1次周测成绩.csv')

In [5]: df.head()
Out[5]: 
   班级   姓名  选科   语文  数学   英语  科目
0   1   吴刚  地理   93  95   82  69
1   1   卢楠  物理  108  77   90  94
2   1  唐秀兰  历史   88  72   95  85
3   1   张刚  化学   85  88  102  76
4   1   姜洋  历史  104  99   84  86
  • 该校高三年级中是否存在姓名相同的学生?

  • 在第一次周测中,请求出每个班级选修物理或化学同学的语数英总分的平均值。哪个班级最高?

  • 学生在该学期的总评计算方式是各次考试总分的加权平均值,其中周测成绩权重为50%(每次测验权重相等,即3.125%),期中权重为20%,期末权重为30%。请结合nlargest函数找出年级中总评前十的同学。

  • 请统计1班到8班文理科(物化生为理科,政史地为文科)期末考试总分前5的学生,结果格式如下,括号内的为选科分数:

In [6]: pd.DataFrame(
   ...:     {
   ...:         "1班(文)": ["王大锤:历史(102)"]+["..."]* 4,
   ...:         "1班(理)": ["..."]* 5,
   ...:         "2班(文)": ["..."]* 5,
   ...:         "...": ["..."]* 5,
   ...:         "8班(理)": ["..."]* 5,
   ...:     }
   ...: ) # 王大锤:历史(102)只是举个例子,表示结果字符串需要按照这个格式来写
   ...: 
Out[6]: 
         1班(文) 1班(理) 2班(文)  ... 8班(理)
0  王大锤:历史(102)   ...   ...  ...   ...
1          ...   ...   ...  ...   ...
2          ...   ...   ...  ...   ...
3          ...   ...   ...  ...   ...
4          ...   ...   ...  ...   ...
  • 学生成绩的稳定性可以用每次考试在全年级相同选科学生中的总分排名标准差来度量,请计算每个班级的各科学生成绩稳定性的均值,结果格式如下:

In [7]: pd.DataFrame(
   ...:     np.random.rand(11, 6),
   ...:     index=pd.Index(range(1, 12), name="班级"),
   ...:     columns=pd.Index(
   ...:         ["物理", "化学", "生物", "历史", "地理", "政治"],
   ...:         name="选科",
   ...:     )
   ...: )
   ...: 
Out[7]: 
选科        物理        化学        生物        历史        地理        政治
班级                                                            
1   0.461479  0.780529  0.118274  0.639921  0.143353  0.944669
2   0.521848  0.414662  0.264556  0.774234  0.456150  0.568434
3   0.018790  0.617635  0.612096  0.616934  0.943748  0.681820
4   0.359508  0.437032  0.697631  0.060225  0.666767  0.670638
5   0.210383  0.128926  0.315428  0.363711  0.570197  0.438602
6   0.988374  0.102045  0.208877  0.161310  0.653108  0.253292
7   0.466311  0.244426  0.158970  0.110375  0.656330  0.138183
8   0.196582  0.368725  0.820993  0.097101  0.837945  0.096098
9   0.976459  0.468651  0.976761  0.604846  0.739264  0.039188
10  0.282807  0.120197  0.296140  0.118728  0.317983  0.414263
11  0.064147  0.692472  0.566601  0.265389  0.523248  0.093941

Ex3:统计商品的审核情况#

在data/supplement/ex3中存放了两个有关商品审核的信息表,“商品信息.csv”中记录了每个商品的ID号,唯一的识别码以及商品所属的类别,“申请与审核记录.csv”中记录了每个商品的审核信息。已知商品的审核流程如下:由申请人发起商品审核的申请,然后由审核人审核,审核的结果包括通过与不通过两种情况,若商品不通过审核则可以由另一位申请人再次发起申请,直到商品的审核通过。

In [8]: df_info = pd.read_csv('data/supplement/ex3/商品信息.csv')

In [9]: df_info.head()
Out[9]: 
         ID号      识别码  类别
0  ID 000001  CRtXJUK  T1
1  ID 000002  RGSxifC  Q1
2  ID 000003  AboduTp  S1
3  ID 000004  zlpUeMl  S2
4  ID 000005  IVQqhIK  S3

In [10]: df_record = pd.read_csv('data/supplement/ex3/申请与审核记录.csv')

In [11]: df_record.head()
Out[11]: 
         ID号         申请人        申请时间         审核人        审核时间   结果
0  ID 000001  \#+3((52\{  2020-04-19  ~1=6\*183|  2020-05-03  未通过
1  ID 000001  8@75[1|2\*  2020-05-10  15![3\({59  2020-07-17  未通过
2  ID 000001  }!7)(#^0*7  2020-07-28  3`}04}%@75  2020-08-23   通过
3  ID 000002  |*{20#9|}5  2020-01-05  ={`8]03*4+  2020-03-09  未通过
4  ID 000002  4~6%)455`[  2020-03-14  =$-36[)|8]  2020-04-21  未通过
  • 有多少商品最终通过审核?

  • 各类别商品的通过率分别为多少?

  • 对于类别为“T1”且最终状态为通过的商品,平均审核次数为多少?

  • 是否存在商品在上一次审核未完成时就提交了下一次审核申请?

  • 请对所有审核通过的商品统计第一位申请人和最后一位审核人的信息,返回格式如下:

In [12]: pd.DataFrame(
   ....:     {
   ....:         "ID号": ["ID 000001"]+["..."]*3,
   ....:         "类别":["T1"]+["..."]*3,
   ....:         "申请人":["\#+3((52\{"]+["..."]*3,
   ....:         "审核人":["3`}04}%@75"]+["..."]*3
   ....:     },
   ....:     index=[1,2,3,"..."]
   ....: )
   ....: 
Out[12]: 
           ID号   类别         申请人         审核人
1    ID 000001   T1  \#+3((52\{  3`}04}%@75
2          ...  ...         ...         ...
3          ...  ...         ...         ...
...        ...  ...         ...         ...

提示

groupby对象上也定义了head和tail方法。

Ex4:删除同样的行#

现有两张表,请在df1中剔除在df2中出现过的行。

In [13]: 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],
   ....: })
   ....: 

In [14]: df1
Out[14]: 
   A  B  C  D
0  3  2  1  5
1  2  1  2  6
2  2  1  2  6
3  3  3  7  1
4  1  6  7  2
5  3  2  1  5

In [15]: df2 = pd.DataFrame({
   ....:     "A": [2,3,1],
   ....:     "B": [1,9,6],
   ....:     "C": [2,7,7],
   ....:     "D": [6,1,2],
   ....: })
   ....: 

In [16]: df2
Out[16]: 
   A  B  C  D
0  2  1  2  6
1  3  9  7  1
2  1  6  7  2

结果应当如下:

In [17]: pd.DataFrame({
   ....:     "A": [3,3,3],
   ....:     "B": [2,3,2],
   ....:     "C": [1,7,1],
   ....:     "D": [5,1,5],
   ....: })
   ....: 
Out[17]: 
   A  B  C  D
0  3  2  1  5
1  3  3  7  1
2  3  2  1  5

Ex5:统计每个学区的开课数量#

某个城市共有4个学区,每个学区有若干学校,学校之间名字互不相同。每一条记录为该学校开设的课程,一个学校可能有多条记录,每一条记录内部的课程不会重复,但同一学校不同记录之间的课程可能重复。

In [18]: df = pd.read_csv('data/supplement/ex5/school_course.csv')

In [19]: df.head()
Out[19]: 
     Area      School                         Course
0  area_1   school_99                      course_90
1  area_2   school_32                      course_20
2  area_3   school_64                      course_38
3  area_1  school_231  course_9 course_40 course_100
4  area_3  school_147  course_57 course_77 course_28

课程的种类共有100门,编号为”school_1”到”school_100”。现要统计每个学区各项课程的开设学校数量,结果如下格式:

In [20]: res = pd.DataFrame(
   ....:     0, index=["course_%d"%(i+1) for i in range(100)],
   ....:     columns=["area_%d"%(i+1) for i in range(4)]
   ....: )
   ....: 

In [21]: res.head() # 若area_1共有20所学校开设了course_1,则第一个单元格为20
Out[21]: 
          area_1  area_2  area_3  area_4
course_1       0       0       0       0
course_2       0       0       0       0
course_3       0       0       0       0
course_4       0       0       0       0
course_5       0       0       0       0

Ex6:捕获非零的行列索引#

给定如下的数据框,请返回非零行列组合构成的多级索引。

In [22]: df = pd.DataFrame(
   ....:     [[0,5,0],[2,1,0],[0,0,6],[0,9,0]],
   ....:     index=list("ABCD"), columns=list("XYZ"))
   ....: 

In [23]: df
Out[23]: 
   X  Y  Z
A  0  5  0
B  2  1  0
C  0  0  6
D  0  9  0

In [24]: res = pd.Index([
   ....:     ('X', 'B'),
   ....:     ('Y', 'A'),
   ....:     ('Y', 'B'),
   ....:     ('Y', 'D'),
   ....:     ('Z', 'C')])
   ....: 

In [25]: res
Out[25]: 
MultiIndex([('X', 'B'),
            ('Y', 'A'),
            ('Y', 'B'),
            ('Y', 'D'),
            ('Z', 'C')],
           )

Ex7:分析集群日志#

某公司构建了一个分布式文件集群,它共有134台服务器构成,分别存放在五个机房,R0机房存有23台,R1机房存有16台,R2机房存有47台,R3机房存有30台,R4机房存有18台,每个机房的服务器编号从001开始。运维人员通过日志收集功能得到了如下所示的集群在2022年9月27日的文件历史传输记录,其每一行构成如下:方括号中显示了当前操作是否为向其他服务器发出文件的操作(PUSH)还是接收其他服务器文件的操作(SAVE)及其对应的操作时间。Cluster#R?#???表示了当前操作的机器编号,Cluster#R4#014表示R4机房的第14号机器;再后面的十位字符串代表了传输文件的唯一标识,如果某一个条记录为SAVE操作的机器接收了XXX文件,那么一定会有另一台机器PUSH这个XXX文件的记录;对于PUSH记录而言,最后的信息表示发出文件的大小,对于SAVE记录而言,最后的信息表示接收到文件的大小,若同一对PUSH记录和SAVE记录的文件大小不一致,那么表明本次文件传输最终处于未完成状态(Unfinished)。

In [26]: with open("data/supplement/ex7/logs.txt", "r") as f:
   ....:     for i, txt in enumerate(f.readlines()):
   ....:         if i >= 5:
   ....:             break
   ....:         print(txt.strip())
   ....: 
[PUSH|2022-09-27 07:08:12] Cluster#R1#007 | tgfuHOAjDJ | 4.41 GB
[PUSH|2022-09-27 15:11:49] Cluster#R2#027 | AFHAvugnTR | 91.64 MB
[PUSH|2022-09-27 06:54:02] Cluster#R3#016 | cJwLKcNsmA | 489.25 MB
[SAVE|2022-09-27 08:17:00] Cluster#R0#019 | neLAGbGkvd | 7.99 GB
[PUSH|2022-09-27 05:31:50] Cluster#R2#012 | rXZyuYLlEE | 730.45 MB
  • 使用高效方法提取日志中的信息,并注意脏数据的清洗(如时间格式错误和无效数字),将其存放为如下格式,其中push_time按时间顺序。file_id为文件唯一标识,file_size为文件实际大小,save_fize为文件最终被接收的大小,push_from表示PUSH该文件的服务器,push_to表示SAVE该文件的服务器。

In [27]: 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, "..."]
   ....: ) # 数据仅为格式参考,不代表真实数据
   ....: 
Out[27]: 
        file_id file_size save_size push_from push_to           push_time           save_time
0    wfjqoIDhsD      6.35      6.32    A3-007  A2-012 2022-09-27 01:03:55 2022-09-27 01:03:57
1    QigjDSEGje    149.23       NaN    A0-017     NaN 2022-09-27 01:03:58                 NaT
...         ...       ...       ...       ...     ...                 NaT                 NaT

提示

本质上是把两个一一对应的文件信息进行连接。

  • 一般而言,文件在同一机房内的传输速度会比跨机房的传输速度快。请对于所有传输完成的文件,按照服务器的机房号来计算文件传输的平均速度(MB/s)。格式如下,第i行第j列表示从所有从机房i传到机房j传输完成文件的平均速度,矩阵的对角线值是否要高于非对角线值?

In [28]: 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)],
   ....: ) # 数据仅为格式参考,不代表真实数据
   ....: 
Out[28]: 
          R0        R1        R2        R3        R4
R0  0.575946  0.929296  0.318569  0.667410  0.131798
R1  0.716327  0.289406  0.183191  0.586513  0.020108
R2  0.828940  0.004695  0.677817  0.270008  0.735194
R3  0.962189  0.248753  0.576157  0.592042  0.572252
R4  0.223082  0.952749  0.447125  0.846409  0.699479
  • 题干中提到,并非所有文件都会传输成功,文件传输成功(Finished)当且仅当文件大小等于接收大小;如果日志中出现了只有单条记录的文件,说明当前传输任务为Missed状态;如果文件大小不等于接收大小,说明当前任务为Unfinished状态。其中对于Unfinished状态,可以按照传输的比例超过90%和超过50%分为三档:”Unfinished-Almost”、”Unfinished-Fair”和”Unfinished-Bad”。请计算每个机房的最终状态的比例,格式如下,结果先按照状态Status排序(”Finished”>”Unfinished-Almost”>”Unfinished-Fair”>”Unfinished-Bad”>”Missed”),再按照机房号排序。

In [29]: 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]
   ....: )
   ....: 
Out[29]: 
       Status Room Ratio
0    Finished   R0  0.10
1    Finished   R1  0.15
2    Finished   R2  0.10
...       ...  ...   ...
24     Missed   R5  0.05
  • 按小时计算每个机房发送的大文件数和接收的大文件数之差,其中大文件指大小超过800M的文件。结果的行索引是时间,列索引是机房。

  • 按小时计算每台机器的空闲率,对于某一台机器而言,空闲时间指其既没有处于发送任务中又没有处于接收任务中的时间,每个小时的空闲率指当前小时的区间内空闲时间的占比。结果的行索引是时间,列索引是机器。

提示

本题涉及到了 区间合并问题 ,pandas的区间索引没有定义类似于merge intervals的函数,请阅读 这个回答 来思考如何实现这个功能。