Crie observações horárias comparáveis

PythonBeginner
Pratique Agora

Introdução

Um espaço de exposições da comunidade precisa comparar seus horários de funcionamento de forma justa. Um total maior de visitantes pode refletir apenas mais oportunidades de registro. Primeiro, você comparará duas faixas com o mesmo tempo de observação; depois, criará uma análise reutilizável que distingue as horas de abertura planejadas, as horas registradas e os valores ainda desconhecidos.

Este laboratório pressupõe noções de Python, leitura, filtragem e agrupamento com pandas, além de gráficos de barras com Matplotlib. O WebIDE já tem Python e as bibliotecas necessárias. Todas as contagens são sintéticas: cada linha representa uma hora, não a identidade de uma pessoa. A duração é uma estimativa do autor que inclui leitura e recuperação de erros comuns. Você produzirá arquivos CSV e PNG reais, preservando todas as entradas originais.

Compare duas faixas com o mesmo número de horas observadas

Nesta etapa, você calculará as taxas à mão antes de criar o primeiro gráfico. Use o terminal inferior da WebIDE Tab. Se ele não estiver visível, abra o menu superior esquerdo e selecione Terminal → New Terminal. Os comandos são executados nesse terminal; os arquivos aparecem no Explorer à esquerda.

Entre na pasta de trabalho preparada. cd muda o diretório de trabalho apenas deste terminal, sem afetar outros terminais. pwd mostra o diretório atual. Se um arquivo não for encontrado mais tarde, volte aqui e confira a grafia com ls.

cd /home/labex/project/opening-hours
pwd
/home/labex/project/opening-hours

cat exibe um arquivo de texto sem alterá-lo. Este CSV tem duas observações de uma hora para cada faixa.

cat data/equal.csv
slot,visitors
morning,6
morning,10
evening,8
evening,12

A manhã tem 16 visitantes em duas horas registradas: 16 / 2 = 8 visitantes por hora. A noite tem 20 em duas horas: 20 / 2 = 10. Com denominadores iguais, a noite lidera tanto no total quanto na taxa horária. A unidade da taxa é “visitantes por hora registrada”.

Crie o pequeno script abaixo. O operador > do shell substitui o arquivo indicado. <<'PY' inicia um here-document: o texto é salvo literalmente até uma linha contendo apenas PY. Cole o bloco inteiro, incluindo essa última linha. O arquivo é criado imediatamente, sem precisar salvá-lo em um editor. agg dá nomes às colunas resultantes; size conta as horas observadas. index=False evita gravar o índice de linhas do pandas no CSV. Agg permite salvar um PNG com Matplotlib sem abrir outra janela.

cat > equal.py <<'PY'
import pandas as pd
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

data = pd.read_csv('data/equal.csv')
summary = data.groupby('slot').agg(visitors=('visitors', 'sum'), hours=('visitors', 'size'))
summary['rate'] = summary['visitors'] / summary['hours']
summary.to_csv('results/equal-summary.csv')
summary['rate'].plot.bar(color=['#256b5d', '#b35828'], rot=0)
plt.ylabel('Visitors per recorded hour')
plt.title('Equal recording opportunities: two hours each')
plt.tight_layout()
plt.savefig('results/equal.png', dpi=150)
plt.close()
print(summary)
PY
python3 equal.py
         visitors  hours  rate
slot
evening        20      2  10.0
morning        16      2   8.0

As linhas são ordenadas pelo nome da faixa. Os espaços iniciais da saída podem variar um pouco. No Explorer, abra opening-hours/results/equal.png e examine o gráfico salvo. As duas barras verticais mostram 10 e 8 visitantes por hora registrada. Mantenha os nomes das faixas nas marcações horizontais e o rótulo vertical Visitors per recorded hour. Cores, fontes, dimensões e ordem das faixas podem variar. O PNG é seu resultado real, não uma imagem-alvo pronta. O gráfico deve ser determinístico: para a mesma entrada, executar novamente o script atual deve reproduzir os mesmos pixels. Evite estilos aleatórios e horários atuais na imagem. Os metadados do PNG podem variar. Você escolhe cores, fontes e tamanho.

Um erro comum no nome do arquivo apenas faz o Python informar sua ausência e parar; ele não reinicia seu trabalho. A falha abaixo é intencional.

python3 missing.py

Você deve ver can't open file e No such file or directory. Liste os arquivos da pasta e execute novamente com o nome correto para se recuperar.

ls
python3 equal.py

Autoverificação: as duas faixas usam duas horas de observação. Você ainda não está supondo oportunidades iguais de registro em todos os conjuntos de dados futuros.

Preserve cada hora planejada

Nesta etapa, você relacionará o número de visitantes à hora planejada correspondente. Continue no terminal do WebIDE em /home/labex/project/opening-hours. Cada linha do plano representa uma hora de uma faixa em determinada data. open=1 indica abertura planejada; open=0, fechamento. O par date e slot é uma chave composta: uma coluna sozinha não identifica a linha. As datas usam YYYY-MM-DD e todas são datas válidas do calendário.

cat data/plan.csv
cat data/counts.csv

O arquivo de contagens está intencionalmente em outra ordem. 0 é uma observação real de nenhum visitante. A falta de uma contagem para uma hora de abertura planejada significa registro ausente, não zero visitantes. Horas de fechamento não têm linha de contagem. Cada contagem deve corresponder a uma linha de abertura do plano. As chaves compostas dos dois arquivos devem ser completas e únicas. Não esconda chaves inválidas removendo duplicatas ou inventando contagens.

Uma junção à esquerda (left merge) preserva todas as linhas da tabela à esquerda: o plano. Ela procura contagens pelas duas colunas da chave, não pela posição da linha. Sem correspondência, o pandas adiciona um valor ausente, exibido como NaN. Confira a completude das chaves antes da junção: o pandas pode relacionar chaves ausentes entre si, mas isso não identifica uma hora real. duplicated(keys) detecta pares repetidos; validate='one_to_one' exige a rejeição de relações com duplicatas.

O script usa .loc[condition, column] para atribuir valores apenas às linhas correspondentes. .eq(0) e .eq(1) comparam cada valor com zero ou um; & combina condições; notna() distingue um valor presente de um ausente. A máscara de fechamento vem primeiro, depois a de abertura com registro, por isso um zero real também é observado.

Substitua o observe.py preparado pelo script completo abaixo. sys.argv[1] e [2] leem os dois argumentos de caminho após o nome do script. Path com / compõe os caminhos dos arquivos. mkdir(parents=True, exist_ok=True) cria a pasta de saída quando necessário, permitindo analisar outra pasta de entrada com o mesmo programa. Os dois CSVs de entrada são apenas lidos, sem alterações.

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

source = Path(sys.argv[1])
output = Path(sys.argv[2])
plan = pd.read_csv(source / 'plan.csv')
counts = pd.read_csv(source / 'counts.csv')
keys = ['date', 'slot']
if plan[keys].isna().any().any() or counts[keys].isna().any().any():
    raise ValueError('Every date and slot must be present')
if plan.duplicated(keys).any() or counts.duplicated(keys).any():
    raise ValueError('Every date/slot key must be unique')
if not plan['open'].isin([0, 1]).all():
    raise ValueError('open must be 0 or 1')
if counts['visitors'].isna().any() or (counts['visitors'] < 0).any():
    raise ValueError('Recorded counts must be nonnegative')
known = counts.merge(plan, on=keys, how='left', validate='one_to_one')
if known['open'].isna().any() or (known['open'] != 1).any():
    raise ValueError('Counts must match an open planned hour')
hours = plan.merge(counts, on=keys, how='left', validate='one_to_one')
hours['status'] = 'missing'
hours.loc[hours['open'].eq(0), 'status'] = 'closed'
hours.loc[hours['open'].eq(1) & hours['visitors'].notna(), 'status'] = 'observed'
hours['valid_hour'] = hours['status'].eq('observed').astype(int)
output.mkdir(parents=True, exist_ok=True)
hours.sort_values(keys).to_csv(output / 'hours.csv', index=False)
print(hours[['date', 'slot', 'visitors', 'status']].head())
PY

A primeira máscara marca horas fechadas. A segunda marca como observadas as horas abertas com contagem, incluindo zero real. & combina duas condições elemento a elemento; notna() testa a presença do valor. valid_hour transforma a condição observada em 1 ou 0, cuja soma conta as horas utilizáveis de observação. Contagens ausentes não são preenchidas com zero.

python3 observe.py data results
head -n 10 results/hours.csv

head -n 10 mostra o cabeçalho e as primeiras nove linhas de dados. O CSV salvo contém date,slot,open,visitors,status,valid_hour, ordenado pela chave composta. Examine-o também no Explorer. O zero registrado de quiet tem observed e valid_hour=1; os visitantes em branco de unlogged têm missing e valid_hour=0; a contagem em branco de closed tem closed. Um campo vazio significa desconhecido ou não aplicável conforme o estado, não um zero presumido.

Autoverificação: cada linha do plano aparece exatamente uma vez em hours.csv, e cada contagem se relaciona à sua própria data e faixa. Se surgir ValueError, leia qual relação é inválida. Confira o script com este código e com as entradas inalteradas, corrija-o e execute novamente. Não é preciso reabrir o terminal.

Calcule a taxa horária e a cobertura dos registros

Nesta etapa, você resumirá os dados por faixa sem mudar o significado de contagens desconhecidas. Continue no mesmo terminal do WebIDE e na mesma pasta de trabalho.

evening tem quatro horas de abertura planejadas, das quais três registram 12 visitantes cada. O total é 36, as horas observadas são 3, a taxa horária é 36 / 3 = 12 e a cobertura é 3 / 4 = 0.75: foram registradas 75% das horas de abertura planejadas. A hora ausente não entra no denominador da taxa, mas reduz a cobertura. morning tem 40 visitantes em quatro horas observadas: total maior, porém taxa menor, de 10.

quiet tem quatro zeros registrados: total 0 e taxa 0, com cobertura completa. unlogged não tem horas observadas: a cobertura é 0, mas o total e a taxa continuam em branco. closed tem 0 horas de abertura planejadas, portanto cobertura e taxa são indefinidas e ficam em branco. Mais tarde, não transforme valores indefinidos em barras de taxa com altura zero.

Acrescente o bloco abaixo uma vez a observe.py. >> acrescenta em vez de substituir. Ele usa hours e output criados acima. sum(min_count=1) retorna uma soma ausente se todos os valores estiverem ausentes; a soma padrão retorna zero mesmo sem observações válidas. replace(0, float('nan')) converte denominadores zero em valores explicitamente ausentes antes da divisão. Isso preserva numeradores zero que foram realmente observados.

cat >> observe.py <<'PY'

grouped = hours.groupby('slot')
summary = grouped.agg(planned_hours=('open', 'sum'), observed_hours=('valid_hour', 'sum'))
summary['visitors'] = grouped['visitors'].sum(min_count=1)
summary['coverage'] = summary['observed_hours'] / summary['planned_hours'].replace(0, float('nan'))
summary['rate'] = summary['visitors'] / summary['observed_hours'].replace(0, float('nan'))
summary.to_csv(output / 'summary.csv')
print(summary)
PY
python3 observe.py data results
cat results/summary.csv
slot,planned_hours,observed_hours,visitors,coverage,rate
afternoon,3,3,36.0,1.0,12.0
closed,0,0,,,
evening,4,3,36.0,0.75,12.0
morning,4,4,40.0,1.0,10.0
quiet,4,4,0.0,1.0,0.0
short,4,1,20.0,0.25,20.0
sparse,5,3,27.0,0.6,9.0
unlogged,4,0,,0.0,

Os números do CSV podem aparecer como 36 ou 36.0 sem mudar o resultado. As colunas obrigatórias e seus significados são mantidos. short tem a maior taxa observada, mas somente uma hora de observação. sparse tem três observações, porém cobre apenas 60% do tempo planejado. Isso explica por que avaliar a suficiência dos dados antes de recomendar. Neste laboratório, você ainda não declara nenhum deles vencedor.

Abra results/summary.csv no Explorer. Preserve data/plan.csv, data/counts.csv, data/equal.csv e os scripts. O contrato reutilizável é python3 observe.py INPUT_DIRECTORY OUTPUT_DIRECTORY: lê dois CSVs da entrada e grava hours.csv e summary.csv na saída. Cálculos equivalentes e outra ordem de linhas são permitidos se todas as chaves e relações numéricas forem preservadas. Se você substituiu o script em vez de acrescentar, recrie o script da etapa 2, acrescente este bloco uma vez e execute novamente.

Autoverificação: uma faixa sem observações e uma faixa com zero realmente observado têm totais, denominadores e taxas diferentes. No próximo laboratório, você usará essa distinção para decidir se os dados sustentam uma comparação.

Resumo

Você criou um gráfico com oportunidades iguais de observação, relacionou as contagens pela chave composta completa e produziu arquivos CSV por hora e por faixa. Horas de fechamento, registros ausentes e zeros reais continuam distintos. A taxa usa horas de abertura registradas; a cobertura mostra a proporção das oportunidades planejadas que foram registradas.

Guarde a análise reutilizável e os dados originais. Essas taxas históricas descritivas não comprovam o que acontecerá ao ampliar o horário de funcionamento. No próximo ambiente independente, você aplicará uma regra explícita de suficiência dos dados antes de recomendar.