Einführung
Eine hohe Besuchersumme allein bestimmt nicht das beste Öffnungszeitfenster. Auch eine hohe Stundenrate aus nur einer Aufzeichnung ist eine schwache Entscheidungsgrundlage. Du wendest eine ausdrückliche Regel der Ausstellung an, um ausreichende Aufzeichnungen zu erkennen, alle gleichauf liegenden Spitzenwerte zu behalten und die Grenzen zusammen mit einem echten Diagramm zu vermitteln.
Dieses Lab setzt das vorherige Lab zu stündlichen Beobachtungen oder gleichwertiges Wissen über zusammengesetzte Schlüssel, fehlende Aufzeichnungen, Stundenraten und Abdeckung sowie Grundlagen in Python, pandas und Matplotlib voraus. Die Umgebung startet unabhängig mit synthetischen Originaldaten und einer vollständigen observe.py; Dateien aus der vorherigen Umgebung werden nicht benötigt. Die Dauer ist eine Autorenschätzung, keine gemessene Bearbeitungszeit.
Eine ausdrückliche Evidenzregel anwenden
In diesem Schritt trennst du die Zulässigkeit vom Ranking. Öffne im WebIDE-Tab das Terminal unten oder wähle oben links Terminal → New Terminal. Beginne im vorbereiteten Arbeitsbereich und führe die mitgelieferte Beobachtungsanalyse aus. Ihre beiden Argumente sind das Ein- und das Ausgabeverzeichnis.
cd /home/labex/project/opening-hours
python3 observe.py data results
cat results/summary.csv
Die Zusammenfassung behält jedes Zeitfenster. morning hat eine Summe von 40, aber die Rate 10; afternoon und evening haben jeweils Rate 12. short hat Rate 20 aus nur einer aufgezeichneten Stunde. sparse hat drei aufgezeichnete Stunden, aber Abdeckung 0.6. Bei quiet wurde tatsächlich null beobachtet, während die Rate von unlogged unbekannt ist.
Die Übungsregel ist öffentlich: Ein Zeitfenster ist nur dann zum Vergleich zugelassen, wenn es mindestens drei beobachtete Stunden und mindestens 0.75 Abdeckung hat. Gleichheit erfüllt die Regel: Drei Beobachtungen von vier geplanten Stunden erreichen beide Grenzen. Vergleiche zulässige Zeitfenster anhand der Besucher pro beobachteter Stunde. Behalte alle Zeitfenster, deren absoluter Abstand zur höchsten zulässigen Rate höchstens 1e-9 beträgt. Diese kleine Toleranz behandelt die Gleitkommadarstellung, keine bedeutsamen betrieblichen Unterschiede. Gibt es keine zulässigen Zeitfenster, schiebe die Empfehlung auf. Dies sind Entscheidungsregeln der Übung, keine allgemeingültigen statistischen Vertrauensschwellen.
Jedes Zeitfenster behält außerdem einen Grund. Nutze diese Priorität: no_open_hours, wenn die geplanten Stunden null sind; sonst too_few_observations, wenn weniger als drei Stunden beobachtet wurden; sonst low_coverage unter 0.75; andernfalls eligible. So unterscheidest du ein geschlossenes Zeitfenster, eines ohne Aufzeichnungen und eines mit genügend Aufzeichnungen, aber zu geringer Abdeckung. Entferne unzulässige Zeitfenster niemals aus der Vergleichstabelle.
set_index('slot') verwendet jeden stabilen Zeitfensternamen als Zeilenbeschriftung. Die Subtraktion der höchsten Kandidatenrate mit anschließendem .abs().le(1e-9) erhält alle Raten innerhalb der angegebenen Toleranz. sorted sorgt für eine stabile Darstellungsreihenfolge der Zeitfensterliste in der Entscheidung.
Erstelle recommend.py. Wie zuvor ersetzt > die Datei, und das zitierte Here-Dokument speichert alle Zeilen bis PY wörtlich. sys.argv liest den Pfad zur Zusammenfassung und das Ausgabeverzeichnis. .ge() bedeutet größer oder gleich; die Masken klassifizieren ganze Spalten. None wird zu JSON null, wenn keine Rate ausgewählt ist. JSON ist ein strukturiertes Textformat; json.dumps erhält die Liste der Zeitfensteridentitäten. Ein Gleichstand bleibt deshalb eine Liste und wird nicht auf die zufällig erste Zeile reduziert.
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
}
Beide gleichauf liegenden Zeitfenster bleiben erhalten. short ist trotz hoher Rate kein Kandidat; sparse wird wegen geringer Abdeckung ausgeschlossen. quiet bleibt mit Rate null zulässig: Wenig Betrieb macht Daten nicht ungültig. Wären alle zulässigen Raten null, lägen alle zulässigen Zeitfenster bei null gleichauf und könnten trotzdem verglichen werden. Wäre keines zulässig, wäre das korrekte Ergebnis status: defer, eine leere slots-Liste und rate: null.
Selbstkontrolle: Öffne results/comparison.csv im Explorer und finde den Grund für jedes Zeitfenster. Der Aufrufvertrag lautet python3 recommend.py SUMMARY_CSV OUTPUT_DIRECTORY. Das Programm muss auch mit einer anderen korrekt erzeugten Zusammenfassung funktionieren und darf nicht von festen Zeitfensternamen oder einem festen Gewinner abhängen.
Die Evidenz neben der Empfehlung zeigen
In diesem Schritt machst du Summen und Raten gemeinsam sichtbar, ohne fehlende Beobachtungen als null auszugeben. Verwende dasselbe WebIDE-Terminal unter /home/labex/project/opening-hours weiter.
Der Diagrammvertrag ist bewusst klein: zwei Darstellungen mit senkrechten Balken, Zeitfensteridentitäten an den horizontalen Achsenmarkierungen und die vertikalen Beschriftungen Total visitors sowie Visitors per recorded hour. Zeige für jedes Zeitfenster beobachtete Stunden und Abdeckung, auch für ausgeschlossene. Eine fehlende Summe oder Rate hat keinen numerischen Balken und wird als unknown markiert. Farben, Schriftarten, Abmessungen und Reihenfolge dürfen variieren. Das Diagramm muss deterministisch sein: Unveränderte Eingaben und das aktuelle Skript erzeugen dieselben Pixel; PNG-Metadaten dürfen abweichen. Verwende weder zufällige Stile noch die aktuelle Zeit im Bild.
Die Schleife nimmt mit zip jeweils eine Achse, einen Spaltennamen und eine Einheitenbeschriftung gemeinsam. summary.iterrows() durchläuft jedes Zeitfenster mit seiner Zeile; enumerate ergänzt die numerische Balkenposition. Das f-String-Format :.0% zeigt einen Bruch als Prozentwert ohne Nachkommastellen. pd.notna prüft vor der Formatierung, ob die Abdeckung vorhanden ist. annotate platziert Text an einer Datenposition; xytext=(0, 5) zusammen mit textcoords='offset points' verschiebt ihn um fünf Darstellungspunkte nach oben. ha='center' zentriert den Text, und fontsize=7 hält die Evidenzbeschriftungen kompakt. Keine dieser Darstellungsoptionen verändert die Berechnung.
Hänge den folgenden Block einmal an recommend.py an; >> erhält den vorhandenen Code. subplots erzeugt zwei Achsen. range(len(summary)) liefert für jedes Zeitfenster eine Balkenposition. Nutze n=N; cov=P% für beobachtete Stunden und auf ganze Prozent dargestellte Abdeckung (cov=n/a, wenn sie undefiniert ist). Jede Beschriftung zeigt, wie viel Evidenz diese Position stützt. Auch die tatsächliche Rate eines unzulässigen Zeitfensters wird dargestellt; sein Grund und die beigefügte Entscheidung verhindern, dass sie als Empfehlung missverstanden wird. Fehlende Raten bleiben fehlend.
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
Öffne results/comparison.png im WebIDE-Explorer. Lies Summen und Raten gemeinsam: Die höhere Gesamtsumme am Morgen bedeutet nicht die höchste Stundenrate; die Raten am Nachmittag und Abend sind höher. Bei short steht sichtbar n=1, bei sparse cov=60%. quiet ist eine echte Null, während die Raten von unlogged und closed unbekannt sind. Ein Balken bei null darf diese unbekannten Werte nicht ersetzen.
cat results/advice.txt
Decision: compare
Slots: afternoon, evening
Evidence: historical description
Effect of extending hours: not established
Der Rat nutzt vier öffentliche Felder: Decision ist compare oder defer, Slots enthält kommaseparierte stabile Zeitfenster-IDs oder none, Evidence ist historical description und Effect of extending hours ist not established. Feld- und Zeitfensterreihenfolge dürfen variieren; danach kannst du Erläuterungen ergänzen. Diese kleinen strukturierten Felder halten den Rat mit der tatsächlichen Entscheidung in Einklang und nennen seine Interpretation, ohne sich nur auf einen überzeugenden Absatz zu verlassen.
Die Empfehlung beschränkt sich auf die beobachteten historischen Möglichkeiten und die ausdrückliche Regel. Sie zeigt nicht, dass geänderte Öffnungszeiten dieselbe Besucherrate verursachen. Ein sinnvoller nächster Schritt ist, fehlende Aufzeichnungen zu ergänzen, bevor über schwach erfasste Zeitfenster entschieden wird. advice.txt vermittelt das Ergebnis; die stündlichen Beziehungen, die Vergleichstabelle, die Entscheidung und die tatsächlich gezeichneten Daten liefern die Grundlage.
Bewahre beide Skripte, beide Original-CSV-Dateien, hours.csv, summary.csv, comparison.csv, decision.json, das tatsächliche PNG und den Rat auf. Ist ein Dateiname falsch, lies den Fehler, suche mit ls und starte den richtigen Pfad. Hast du die Datei ersetzt statt ergänzt, erstelle das Skript aus Schritt 1 erneut und hänge diesen Block einmal an. Beide Programme berechnen bei erneutem Aufruf ihre Ausgaben neu, ohne die Eingaben zu ändern.
Selbstkontrolle: Die Diagrammwerte stammen aus der aktuellen Zusammenfassung, jeder Gleichstand ist enthalten und die Einschränkung begleitet das Ergebnis. In der Challenge ändern sich Zeitfensteridentitäten und Evidenz eines anderen Orts, aber nicht diese Analyseregeln.
Zusammenfassung
Du hast die Zulässigkeit vom Ranking getrennt, alle gleichauf liegenden Spitzenwerte behalten und für jedes ausgeschlossene Zeitfenster einen Grund erhalten. Zwei echte Balkendiagramme zeigen die gesamte Besucherzahl neben Besuchern pro aufgezeichneter Stunde; Beobachtungszahlen und Abdeckung sind sichtbar. Unbekannte Raten bleiben unbekannt, und der Rat nennt die kausale Grenze beschreibender historischer Daten.
Deine Skripte können die Ergebnisse eines anderen Veranstaltungsorts neu berechnen. Eine korrekte Analyse darf eine Empfehlung aufschieben, mehrere Gleichstände behalten oder ein wirklich ruhiges, aber zulässiges Zeitfenster vergleichen. Sie muss keinen einzelnen spektakulären Gewinner hervorbringen.



