第八章

零、练一练

练一练

对于序列a=pd.Series([[1,2], [4,5]]),转为string类型后使用str切片,即a.astype("string").str[::-1],与直接使用str切片,即a.str[::-1],它们的结果分别是什么?请进行相应的解释。

a = pd.Series([[1,2], [4,5]])

当转为字符串后,每个单元格的元素都会更换为元素调用__str__()返回的字符串对象,此时切片是逐个字符串元素的切片:

a.iloc[0].__str__()
'[1, 2]'
a.astype("string").str[::-1]
0    ]2 ,1[
1    ]5 ,4[
dtype: string

当直接使用切片时,等价于直接对每个元素进行[::-1]操作,由于内部存储的时列表,因此每个对应位置返回了列表的反转:

a.str[::-1]
0    [2, 1]
1    [5, 4]
dtype: object

练一练

使用如下的语句可从my_file.txt中读取文本:

with open("data/ch8/my_file.txt", "w") as f:
    text = f.read()
    # 进行后续文本操作

使用如下的语句可将文本保存到my_file.txt中:

text = "aaa\nbbb"
with open("my_file.txt", "w") as f:
    f.write(text)

请结合正则表达式相关的知识,完成以下内容:

  • 读取data/ch8/regex1.txt中的数据(文本均随机生成),删除“#begin”和“#close”所在行及其中间的所有行,处理后保存至data/ch8/regex1_result.txt中。

  • 读取data/ch8/regex2.txt中的数据,将“\section{×××}”、“\subsection{×××}”和“\subsubsection{×××}”分别替换为“# ×××”、“## ×××”和“### ×××”,其中“×××”指代花括号中的标题名,处理后保存至data/ch8/regex2_result.txt中。

  • 1

import re
with open("data/ch8/regex1.txt", "r") as f:
    text = f.read()
    res = re.sub("#begin[\S\s]*?#close", "", text)
with open("data/ch8/regex1_result.txt", "w") as f:
    f.write(res)
  • 2

with open("data/ch8/regex2.txt", "r", encoding="utf-8") as f:
    text = f.read()
    res = re.sub(r"\\((?:sub)*)section\{([\s\S]+?)\}", r"sub\1 \2", text)
    res = re.sub(r"sub", r"#", res)
with open("data/ch8/regex2_result.txt", "w") as f:
    f.write(res)

练一练

在上述的两个负向断言例子中,如果把“[件|\d]”和“[少|\d]”分别修改为“件”和“少”,此时匹配结果如何变化?请解释原因。

在第一个例子中,想要匹配的是“元”前面的那个数字,不记录“件”前面的那个数字,但如果此时不剔除\d,那么此时50这个数字符合\d\d,能够匹配(\d+)(?!件),因此5出现在了结果中。在第二个例子中,造成0匹配的原因类似,50恰好能够匹配(?<!少)(\d+),因此返回的结果中包括0.

一、房屋数据的文本提取

现有一份房屋信息数据集如下:

df = pd.read_csv('data/ch8/house.csv')
df.head(3)
floor year area price
0 高层(共6层) 1986年建 58.23㎡ 155万
1 中层(共20层) 2020年建 88㎡ 155万
2 低层(共28层) 2010年建 89.33㎡ 365万
  • 将year列改为整数年份存储。

  • 将floor列替换为Level、Highest两列,其中的元素分别为string类型的层类别(高层、中层、低层)与整数类型的最高层数。

  • 计算每平米均价avg_price,以××元/平米的格式存储到表中,其中××为整数。

【解答】
df = pd.read_csv('data/ch8/house.csv')
df.year = pd.to_numeric(df.year.str[:-2]).astype('Int64')
df.head()
floor year area price
0 高层(共6层) 1986 58.23㎡ 155万
1 中层(共20层) 2020 88㎡ 155万
2 低层(共28层) 2010 89.33㎡ 365万
3 低层(共20层) 2014 82㎡ 308万
4 高层(共1层) 2015 98㎡ 117万
pat = '(\w层)(共(\d+)层)'
new_cols = df.floor.str.extract(pat).rename(columns={0:'Level', 1:'Highest'})
df = pd.concat([df.drop(columns=['floor']), new_cols], axis=1)
df.head()
year area price Level Highest
0 1986 58.23㎡ 155万 高层 6
1 2020 88㎡ 155万 中层 20
2 2010 89.33㎡ 365万 低层 28
3 2014 82㎡ 308万 低层 20
4 2015 98㎡ 117万 高层 1
s_area = pd.to_numeric(df.area.str[:-1])
s_price = pd.to_numeric(df.price.str[:-1])
df['avg_price'] = ((s_price/s_area)*10000).astype('int').astype('string') + '元/平米'
df.head()
year area price Level Highest avg_price
0 1986 58.23㎡ 155万 高层 6 26618元/平米
1 2020 88㎡ 155万 中层 20 17613元/平米
2 2010 89.33㎡ 365万 低层 28 40859元/平米
3 2014 82㎡ 308万 低层 20 37560元/平米
4 2015 98㎡ 117万 高层 1 11938元/平米

二、巴洛克作曲家的年龄统计

巴洛克时期是西方音乐的发展过程中的重要时期,它上承文艺复兴时期,下启古典主义时期,期间诞生了许多伟大的作曲家。在data/ex-ch8-2-baroque.txt中存放了巴洛克作曲家(含部分文艺复兴晚期作曲家)的名字和生卒年份:

df = pd.read_table("data/ch8/baroque.txt")
df.head()
Information for Baroque Composers
0 Philippe de Monte (1521-1603)
1 Baldassare Donato (1525/1530-1603)
2 Costanzo Porta (1529-1601)
3 Jiří Rychnovský (1529-1616)
4 Guillaume Costeley (1530-1606)
  • 请筛选出能够确定出生与去世年份的作曲家,并提取他们的姓名、出生年和去世年。

  • 约翰.塞巴斯蒂安.巴赫(Johann Sebastian Bach)是重要的巴洛克作曲家,请问在数据表中寿命超过他的作曲家比例为多少?

【解答】
  • 1

df = pd.read_table("data/ch8/baroque.txt")
pat = '(?P<Name>[\w\s]+)\s\((?P<birth>\d{4})-(?P<death>\d{4})\)'
res = df.iloc[:, 0].str.extract(pat).dropna().reset_index(drop=True)
res.head()
Name birth death
0 Philippe de Monte 1521 1603
1 Costanzo Porta 1529 1601
2 Jiří Rychnovský 1529 1616
3 Guillaume Costeley 1530 1606
4 Mateo Flecha the Younger 1530 1604
  • 2

res.birth = res.birth.astype("int")
res.death = res.death.astype("int")
bach = res.query("Name=='Johann Sebastian Bach'").iloc[0]
bach_age = bach.death - bach.birth
((res.death - res.birth) > bach_age).mean()
0.5014044943820225

三、汇总显卡测试的结果

在data/ch8/benchmark.txt文件中记录了RTX3090显卡某次性能测评的日志结果,每一条日志有如下结构:

Benchmarking #2# #4# precision type #1#
#1#  model average #2# time :  #3# ms

其中#1#代表的是模型名称,#2#的值为train(ing)或inference,表示训练状态或推断状态,#3#表示耗时,#4#表示精度,其中包含了float、half、double这3种类型,下面是一个具体的例子:

Benchmarking Inference float precision type resnet50
resnet50  model average inference time :  13.426570892333984 ms

请把日志结果进行整理,变换成如下状态,行索引用相应模型名称填充,按照字母顺序排序,数值保留3位小数:

Training_half Training_float Training_double Inference_half Inference_float Inference_double
Three
densenet121 88.976 93.357 417.206 19.772 15.636 144.110
densenet161 144.318 136.624 1290.286 27.554 31.750 511.176
densenet169 121.555 104.839 511.403 26.370 21.598 175.807
densenet201 118.940 129.333 654.365 33.393 26.169 223.960
mnasnet0_5 27.197 28.527 48.232 6.929 8.038 11.870
【解答】
df = pd.read_table('data/ch8/benchmark.txt').iloc[9:-2].reset_index(drop=True)
pat1 = 'Benchmarking (?P<One>Training|Inference) (?P<Two>float|half|double) precision type (?P<Three>\w+)'
pat2 = '.+time :  (?P<Time>\d+\.\d{3}).+'
res1 = df.loc[0::2,'start'].str.extract(pat1).reset_index(drop=True)
res2 = pd.to_numeric(df.loc[1::2,'start'].str.extract(pat2).Time).reset_index(drop=True)
res = pd.concat([res1.One +'_'+ res1.Two, res1.Three, res2],axis=1).set_index([0,'Three'])
res = res.unstack('Three').droplevel(0, axis=1).T
idx = pd.MultiIndex.from_product([['Training','Inference'],['half','float','double']])
idx = idx.map(lambda x:x[0]+'_'+x[1])
res = res.reindex(idx ,axis=1)
res.head()
Training_half Training_float Training_double Inference_half Inference_float Inference_double
Three
densenet121 88.976 93.357 417.206 19.772 15.636 144.110
densenet161 144.318 136.624 1290.286 27.554 31.750 511.176
densenet169 121.555 104.839 511.403 26.370 21.598 175.807
densenet201 118.940 129.333 654.365 33.393 26.169 223.960
mnasnet0_5 27.197 28.527 48.232 6.929 8.038 11.870