Python median_low 与 median_high:中间值算出来了,为什么却不是任何一条观测

前天 3阅读

有四次试运行耗时:二、七、九、二十秒。你想挑一次处于中间位置的试验,交给同事复核,普通中位数却给出八秒。计算没有错,只是八是两条中央观测的平均值,本来就不需要出现在原始数据中。选择一条真实观测与概括一组数值,是两个不同的任务。

statistics 提供 median_low 和 median_high。样本数为偶数时,它们分别选择排序后靠下和靠上的中央值;样本数为奇数时,两者都选唯一的中央值。下面对未排序输入运行完整示例,保存为 demo.py,执行 python demo.py,只依赖标准库。这里统计的是示例秒数,不代表真实性能测试。

Python median_low 与 median_high:中间值算出来了,为什么却不是任何一条观测

AI生成概念插图:一列真实样本的两个中央位置分别高亮,中间空心位置表示插值结果;不是实验照片或运行截图。

from statistics import median, median_low, median_high, StatisticsError
from math import isfinite

def select_middle(values, choose_high=False):
    values = list(values)
    if not values:
        raise ValueError("at least one observation is required")
    if any(type(x) not in (int, float) or not isfinite(x) for x in values):
        raise ValueError("finite int or float observations are required")
    return median_high(values) if choose_high else median_low(values)

data = [20, 2, 9, 7]
snapshot = data.copy()
ordinary = median(data)
low, high = select_middle(data), select_middle(data, True)
assert (ordinary, low, high) == (8.0, 7, 9)
assert ordinary not in data and low in data and high in data
assert data == snapshot
print("median:", ordinary)
print("low:", low)
print("high:", high)
odd = [11, 3, 10]
assert median(odd) == median_low(odd) == median_high(odd) == 10
assert median_low([1, 1, 9, 9]) == 1
assert median_high([1, 1, 9, 9]) == 9
print("odd:", median(odd))
try:
    median_low([])
except StatisticsError:
    print("empty sample rejected")
else:
    raise AssertionError("empty sample accepted")
for bad in [[], [True], [float("nan")], [float("inf")]]:
    try:
        select_middle(bad)
    except ValueError:
        pass
    else:
        raise AssertionError("invalid sample accepted")
print("input checks passed")

三种输出回答不同的问题

第一组打印 median: 8.0、low: 7、high: 9。低中位数不是最小值,高中位数也不是最大值,它们只在两条中央观测之间作确定的选择。代码同时断言七和九存在于输入中、八不在输入中,直接检验了“必须选择一个实际值”的要求。

换成三、十、十一三个数时,三种函数都返回十。因为排序后只有一个中央位置,不再需要在两条之间决定。重复观测仍然分别占据位置,不能为了让列表看起来整齐先转成集合;示例中一、一、九、九的低中位数是一,高中位数是九,去重会改变样本量和统计含义。

函数会处理排序,不要求调用前先把列表改成有序状态。示例还检查原列表仍保持原来的顺序,让采样顺序继续可用。但别据此推断它是适合无限流的在线算法:要确定整批数据的中间位置,仍需处理整批输入,也应考虑实际样本的规模。

先决定选择规则,再解释结论

如果场景要求从现有测试记录里选一个代表值,要预先说明选低还是选高。对于耗时,较低意味着中央两条里较快的那条;对于完成数量,较低却可能意味着较弱的那条。函数名字没有附带保守、乐观或者安全的业务含义,不能看完结果再挑更好看的一个。

只选出值还不一定选出了记录。多次试验可以恰好同样耗时,如果还要报告对应的日志编号,应另写并列选择规则,例如在相同耗时中取编号最小者。不要用一次 list.index 就默默把“最早在数组出现”当成业务标准;原始输入顺序变化以后,选中的记录也会变化。

示例的校验器只接受非空的有限整数或浮点数,明确拒绝布尔值、无穷和非数值。这个限制是本教程的输入契约,不是对库全部能力的描述。现实数据若使用 Decimal 或 Fraction,应当另定一致的数值类型和校验规则,避免不同来源混合后才发现比较或运算不符合预期。

空输入没有可用的中央观测,因此代码确认原始函数会抛出 StatisticsError,而业务入口给出更早的说明。缺失值也不要随手替换为零:这样会增加一条并不存在的观测,可能把中间位置往下移动。是否排除缺失、样本最少要有几条,都需要连同结果一起记录。

最后,选到真实观测值不等于该次试验能代表每个运行条件。先按相同任务、环境和计量单位组成可比较的样本,再计算中央位置。这个小实验验证的是三个函数的确定行为,没有证明样本具有代表性,也没有用少量数据推断总体性能。

资料核对日期:2026年10月2日(北京时间)。示例在本地 Python 3.12.14 实际运行并通过断言,结果仅对应文中给定输入。

参考资料

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。