依据证据推荐开放时段

PythonBeginner
立即练习

介绍

总客流很大,并不能单独说明哪个开放时段最好。仅凭一次记录得到很高的每小时客流率,也不足以支撑稳妥判断。你将应用展览明确的规则,判断哪些比较拥有足够记录证据,保留所有并列最高项,并通过真实图表同时说明结论的限制。

本实验要求完成前面的逐小时观测实验,或具备复合键、缺失记录、每小时客流率和覆盖率的等价知识,并掌握 Python、pandas 和 Matplotlib 基础。环境独立提供原始合成数据和完整的 observe.py,不需要上一环境的文件。时长是作者估计,不是实测完成时间。

应用明确的证据规则

在这一步中,你将把比较资格与排名分开。在 WebIDE Tab 打开底部终端,或从左上角菜单选择 Terminal → New Terminal。进入已准备的工作区,运行提供的观测分析。两个参数分别是输入目录和输出目录。

cd /home/labex/project/opening-hours
python3 observe.py data results
cat results/summary.csv

汇总保留全部时段。morning 总量为 40,但客流率为 10;afternoon 和 evening 的客流率都是 12。short 仅凭一个记录小时得到客流率 20。sparse 有三个记录小时,但覆盖率为 0.6。quiet 的真实观测是零,而 unlogged 的客流率未知。

展览的训练规则是公开的:一个时段只有同时具备至少三个观测小时,并且覆盖率至少为 0.75,才符合比较资格。等于边界也符合要求:四个计划小时中有三个观测,同时达到两个边界。只用每个观测小时的访客数比较合格时段。保留与合格最高客流率绝对差在 1e-9 内的全部时段。这个极小容差用于处理浮点表示,不代表有意义的业务差异。如果没有合格时段,就暂缓建议。这些是练习决策规则,不是普适的统计置信阈值。

每个时段还要保留原因。采用以下优先顺序:计划小时数为零时是 no_open_hours;否则,观测小时少于三个时是 too_few_observations;否则,覆盖率低于 0.75 时是 low_coverage;否则为 eligible。这样能区分关闭时段、完全没有记录的时段,以及记录数量足够但覆盖不足的时段。绝不要从比较表中删除不合格时段。

set_index('slot') 将稳定的时段名称设为行标签。先减去候选最高客流率,再用 .abs().le(1e-9),就能保留规定容差内的全部项;sorted 为决策中的时段列表提供稳定的显示顺序。

创建 recommend.py。与前面一样,> 替换文件,带引号的 here-document 将截至 PY 的所有行原样保存。sys.argv 读取汇总文件路径和输出目录。.ge() 表示大于或等于,条件掩码对整列分类。没有可选客流率时,None 会变为 JSON null。JSON 是结构化文本格式;json.dumps 会保留时段身份列表,因此并列结果是一个列表,而不是偶然排在第一行的项目。

cat > recommend.py <<'PY'
import sys
import json
from pathlib import Path
import pandas as pd

source = Path(sys.argv[1])
output = Path(sys.argv[2])
summary = pd.read_csv(source).set_index('slot')
summary['eligible'] = summary['observed_hours'].ge(3) & summary['coverage'].ge(0.75)
summary['reason'] = 'eligible'
summary.loc[summary['coverage'].lt(0.75), 'reason'] = 'low_coverage'
summary.loc[summary['observed_hours'].lt(3), 'reason'] = 'too_few_observations'
summary.loc[summary['planned_hours'].eq(0), 'reason'] = 'no_open_hours'
candidates = summary.loc[summary['eligible']]
best = float(candidates['rate'].max()) if len(candidates) else None
winners = sorted(candidates.index[(candidates['rate'] - best).abs().le(1e-9)].tolist()) if best is not None else []
decision = {'status': 'compare' if winners else 'defer', 'slots': winners, 'rate': best}
output.mkdir(parents=True, exist_ok=True)
summary.to_csv(output / 'comparison.csv')
(output / 'decision.json').write_text(json.dumps(decision, indent=2) + '\n')
print(summary[['observed_hours', 'coverage', 'rate', 'eligible', 'reason']])
print(json.dumps(decision))
PY
python3 recommend.py results/summary.csv results
cat results/decision.json
{
  "status": "compare",
  "slots": [
    "afternoon",
    "evening"
  ],
  "rate": 12.0
}

两个并列时段都保留下来。short 客流率虽高,却不是候选;sparse 因覆盖不足被排除。quiet 客流率为零,但仍符合资格:安静不代表数据无效。如果所有合格客流率都是零,所有合格时段会以零并列,仍然可以比较。如果每个时段都不合格,正确结果应为 status: defer、空的 slots 列表和 rate: null。

自检:在 Explorer 中打开 results/comparison.csv,找到每个时段的原因。程序调用约定是 python3 recommend.py SUMMARY_CSV OUTPUT_DIRECTORY。它必须能够处理另一份正确生成的汇总,而不是固定时段名或固定赢家。

在建议旁展示证据

在这一步中,你将同时展示总量和客流率,不把缺失观测伪装成零。继续使用 /home/labex/project/opening-hours 下同一个 WebIDE 终端。

图表约定刻意保持简单:两个竖直柱状图面板,横轴刻度标签为时段身份,纵轴标签分别为 Total visitors 和 Visitors per recorded hour。每个时段都要显示观测小时数和覆盖率,包括不合格时段。缺失的总量或客流率没有数值柱,并标为 unknown。颜色、字体、尺寸和时段顺序可以不同。图表必须具有确定性:输入不变时,当前脚本应重现相同像素;PNG 元数据可以不同。不要在图中加入随机样式或当前时间。

循环使用 zip,每次一起取出一个坐标轴、一个列名和一个单位标签。summary.iterrows() 遍历各时段及其行,enumerate 补充该时段的数值柱位置。f-string 格式 :.0% 把小数显示为不带小数位的百分比。pd.notna 在格式化前判断覆盖率是否存在。annotate 在数据位置放置文本;xytext=(0, 5) 配合 textcoords='offset points',将文本向该位置上方移动五个显示点。ha='center' 让文本居中,fontsize=7 使证据标签紧凑。这些显示选项都不改变计算。

将下面代码向 recommend.py 追加一次;>> 保留已有代码。subplots 创建两个坐标轴。range(len(summary)) 为每个时段提供一个柱位置。观测小时数和整数百分比覆盖率使用 n=N; cov=P% 格式,未定义覆盖率时使用 cov=n/a。每个标签都告诉读者该位置有多少证据。图中仍展示不合格时段的实际客流率,但其原因和配套决策会防止读者把它误当作推荐项。缺失客流率仍保持缺失。

cat >> recommend.py <<'PY'

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

fig, axes = plt.subplots(1, 2, figsize=(14, 6))
x = list(range(len(summary)))
for ax, column, label in zip(axes, ['visitors', 'rate'], ['Total visitors', 'Visitors per recorded hour']):
    bars = ax.bar(x, summary[column], color='#26705e')
    ax.set_xticks(x, summary.index, rotation=40, ha='right')
    ax.set_ylabel(label)
    ax.set_ylim(bottom=0)
    ax.grid(axis='y', alpha=0.2)
    for n, (slot, row) in enumerate(summary.iterrows()):
        coverage = f"{row['coverage']:.0%}" if pd.notna(row['coverage']) else 'n/a'
        note = f"n={int(row['observed_hours'])}; cov={coverage}"
        value = row[column]
        if pd.isna(value):
            note = 'unknown\n' + note
        ax.annotate(note, (n, 0 if pd.isna(value) else value), xytext=(0, 5), textcoords='offset points', ha='center', fontsize=7)
fig.suptitle('Opening opportunities: totals and observed-hour rates')
fig.tight_layout(rect=(0, 0.04, 1, 0.93))
fig.savefig(output / 'comparison.png', dpi=150)
plt.close(fig)
choice = ', '.join(winners) if winners else 'none'
advice = f"Decision: {decision['status']}\nSlots: {choice}\n"
advice += 'Evidence: historical description\n'
advice += 'Effect of extending hours: not established\n'
(output / 'advice.txt').write_text(advice)
print(advice)
PY
python3 recommend.py results/summary.csv results

从 WebIDE Explorer 打开 results/comparison.png。结合总量与客流率面板阅读:上午总量更高,并不能胜过下午或晚间的客流率。short 清楚显示 n=1,sparse 显示 cov=60%。quiet 是真实零值,unlogged 和 closed 的客流率则未知。不能用零高度柱代替这两种未知值。

cat results/advice.txt
Decision: compare
Slots: afternoon, evening
Evidence: historical description
Effect of extending hours: not established

建议采用四个公开字段:Decision 是 compare 或 defer,Slots 包含逗号分隔的稳定时段 ID 或 none,Evidence 为 historical description,Effect of extending hours 为 not established。字段和时段顺序可以变化,也可以在这些字段后增加解释。这些简短的结构化字段确保建议与真实决策一致,并明确解释范围,而不是只靠一段有说服力的文字。

建议仅适用于已经观测的历史机会和明确规则。它不能说明改变开放时间会造成相同客流率。对于记录不足的时段,一个有用的后续行动是补充缺失观测后再决定。advice.txt 传达结果;逐小时关系、比较表、决策和实际绘图数据才是支持结果的证据。

保留两个脚本、两个原始输入 CSV、hours.csv、summary.csv、comparison.csv、decision.json、实际 PNG 和建议。文件名错误时,先读错误,用 ls 定位文件,再用正确路径运行。如果误把追加写成替换,请重新创建步骤 1 的脚本,再追加此代码块一次。重新运行任何一个程序都会重新计算输出,而不改变输入。

自检:图表数值来自当前汇总,全部并列项都在,结果附有解释限制。挑战将换成另一个场馆,改变时段身份和证据,但分析规则不变。

总结

你把比较资格与排名分开,保留所有并列最高项,并为每个被排除时段保留原因。两幅真实柱状图并列展示总客流与每个记录小时的客流,同时显示观测数量和覆盖率。未知客流率仍保持未知,建议也说明了历史描述性数据在因果解释上的限制。

脚本能够重新计算另一个场馆的结果。正确分析可以暂缓建议、保留多个并列项,或比较一个确实安静但符合资格的时段;不必总选出一个令人兴奋的唯一赢家。