看过来
《pandas 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
每年各省都会公布高考「一分一段」表,它是是以「一分」为单位,统计考得该分数的考生人数和累计人数,每一个分数段上有多少人一目了然。考生通过分数分布表可以查询到相关成绩在全市的排名位次,方便对自己进行定位。本例中,我们将通过一个过往年份的「一分一段」表,来推断出当前位次在历史表中的分数。
我们来看看数据(部分数据):
import pandas as pd
from io import StringIO
data1 = '''
分数,位次
696,46
695,56
694,65
693,77
692,87
691,95
690,109
689,120
688,136
687,156
686,173
685,206
684,230
683,253
682,286
681,310
680,344
679,383
678,419
677,450
676,501
675,551
674,607
'''
df_old = pd.read_csv(StringIO(data1))
df_old
# ...
data2 = '''
位次
61
519
75
100
'''
df = pd.read_csv(StringIO(data2))
df
# ...
这里有有两张表:
所以,需求是:
这个需求中,假定我们对所有的考试同学从 1 到 n 进行编号,历史表 df_old 的位次,其实是一个排名区间,这个编号区间的同学所考的分数都是这个分数,代表着这个分数从多少到多少人。
可以用 pandas 的 pd.Interval 对象来表达这个区间。在当前表 df 中计算时,我个看位次是否在这个区间时,如果在此区间内则取对应的分数。
先将历史表位次转为对应的区间对象,形成一个映射表:
rng_map = (
# 增加辅助列,前一同学编号
df_old.assign(pre=df_old.位次.shift().add(1).fillna(1))
.set_index('分数')
# 转为区间对象
.apply(lambda x: pd.Interval(x.pre, x.位次, closed='both'), axis=1)
)
rng_map.head()
'''
分数
696 [1.0, 46.0]
695 [47.0, 56.0]
694 [57.0, 65.0]
693 [66.0, 77.0]
692 [78.0, 87.0]
dtype: interval
'''
然后编写一个函数供 df 当前表的位次来调用:
def func(val: int):
selected = [(val in i) for i in rng_map]
foo = df_old[selected].分数.squeeze()
return foo
selected 为一个布尔序列,为 True 的表示在区间里,用其筛选历史表,得到了所在区间的一行数据,再用 squeeze() 降维转为标量返回。
调用函数来增加新列:
df.assign(历史分数=df.位次.map(func))
'''
位次 历史分数
0 61 694
1 519 675
2 75 693
3 100 690
'''
可以通过当前位次与历史位次查询的方法,取到最小的第一个分数值的方法。
# 指定位次小于等于历史位次,并取第一个
df_old[61 <= df_old.位次].head(1)
'''
分数 位次
2 694 65
'''
# 取标量
df_old[61 <= df_old.位次].head(1).分数.squeeze()
# 694
# 转为匿名函数调用
df.assign(历史分数=df.位次.map(lambda x: df_old[x <= df_old.位次]
.head(1).分数.squeeze()
)
)
# ...
这样,我们就完成了需求。
(完)
更新时间:Aug. 18, 2024, 4:01 p.m. 标签:pandas python 分数 区间