Faça um script que leia um arquivo ‘.dat’ e plot gráficos. Para isso:
(a) Peça para o usuário passar o nome do arquivo: teste se o arquivo existe (use a função isfile());
(b) Leve em consideração que o código não sabe se os sinais estão em linhas ou colunas, só que o número de amostras por sinal é maior que o número de sinais distintos;
(c) Leve em consideração que o código não sabe quantas linhas e colunas tem no arquivo passado;
(d) Leve em consideração que o primeiro sinal é o eixo x e os demais são os sinais ( y ) a serem plotados;
(e) Pergunte ao usuário se prefere que os plots sejam todos na mesma figura ou em figuras distintas;
  • Se forem plotados na mesma figura: coloque legenda no gráfico nomeando cada um como ‘sinal 01’, ‘sinal 02’, …, ‘sinal 0n’;
  • Se forem plotados em figuras separadas: coloque o título ‘Plotando o Sinal 01’, ‘Plotando o Sinal 02’, … ‘Plotando o Sinal 0n’;
    (f) Coloque etiquetas (labels) nos eixos;
    (g) Mude os limites dos eixos (axis) de forma automática para que contenha de forma harmoniosa todos os gráficos;
    (h) Teste o script com os arquivos gerados na AA1-Questão2, para isso, modifique script da AA1-Questão2 para ele consiga gerar arquivos cujos dados possam ser salvos nas linhas ou nas colunas do documento (dependendo do que o usuário escolha) para poder testar melhor o script feito nesta questão. Adicione esses scripts modificados junto com a resposta da questão.

Passo 1

Vou listar claramente o que o enunciado pede e as informações relevantes (sem resolver nada ainda).
  • O script principal deve:
    • (a) Pedir ao usuário o nome do arquivo e testar se ele existe usando a função isfile() (do módulo os.path).
    • (b) Considerar que os sinais no arquivo podem estar armazenados em linhas ou em colunas; o código não sabe isso a priori. A única pista que temos é que o número de amostras por sinal é maior que o número de sinais distintos.
    • (c) Não saber quantas linhas e colunas existem no arquivo.
    • (d) Assumir que o primeiro sinal é o eixo x e os demais são os sinais y a serem plotados.
    • (e) Perguntar ao usuário se quer todos os plots na mesma figura ou em figuras distintas. Se na mesma figura, usar legenda com nomes 'sinal 01', 'sinal 02', …; se em figuras separadas, usar títulos 'Plotando o Sinal 01', 'Plotando o Sinal 02', …
    • (f) Colocar labels nos eixos.
    • (g) Ajustar automaticamente os limites dos eixos para conter harmonicamente todos os gráficos (usar margens).
    • (h) Acrescentar scripts que gerem arquivos .dat de teste (modificação da AA1-Questão2) capazes de salvar os dados em linhas ou colunas conforme escolha do usuário, para testar o script principal.
  • Observações importantes que vou usar:
    • Ao carregar o arquivo com numpy, obtemos um array com forma (n_{\text{rows}}, n_{\text{cols}}). Se os sinais estão armazenados em linhas, cada linha é um sinal e n_{\text{cols}} será o número de amostras. Se estão em colunas, cada coluna é um sinal e n_{\text{rows}} será o número de amostras.
    • A regra prática para detectar a orientação será comparar n_{\text{rows}} e n_{\text{cols}}: o lado maior corresponde a número de amostras (pois “número de amostras por sinal é maior que o número de sinais”). Se n_{\text{cols}} > n_{\text{rows}} então os sinais estão em linhas; se n_{\text{rows}} > n_{\text{cols}} então estão em colunas. Se houver empate, pediremos ao usuário que escolha (não dá para inferir).
    • Sempre que eu usar símbolos matemáticos, vou indicá-los como x, y, n_{\text{rows}}, n_{\text{cols}}.

Passo 2

Explicação da estratégia de implementação (algoritmo) — um “mapa” passo a passo do que o script fará, com conceitos básicos explicados.
  • Passos do script principal:
    1. Pedir ao usuário o nome do arquivo e verificar existência com os.path.isfile. Se não existir, sair com mensagem clara.
      • Ensino rápido: isfile(path) retorna True se path existe e é um arquivo regular. Importante para evitar erro ao tentar abrir arquivo inexistente.
    2. Carregar os dados do arquivo em um array NumPy usando np.genfromtxt() (mais tolerante a linhas incompletas/ comentários) ou np.loadtxt() (se formato bem regular). Tratamento dentro de try/except.
      • Ensino rápido: np.genfromtxt('arquivo.dat', delimiter=None) tenta adivinhar delimitador (espaços/tabs) e ignora linhas que começam com # se comments='#'. Retorna um array 1D ou 2D.
    3. Verificar dimensionalidade do array:
      • Se data.ndim == 1 significa que o arquivo resultou em um vetor (por exemplo: apenas uma linha sem separadores suficientes). Nesse caso, perguntar ao usuário se o formato é “uma linha com várias colunas” (sinais nas colunas) ou “uma coluna com várias linhas” (sinais nas linhas). Explicarei interativamente.
      • Se data.ndim == 2 aplicar a regra de detecção da orientação por comparação de n_{\text{rows}}, n_{\text{cols}}.
      • Ensino rápido: data.shape fornece (n_{\text{rows}}, n_{\text{cols}}). Indexação em NumPy: data[i, j] é o elemento da linha i, coluna j.
    4. Após identificar orientação:
      • Se sinais em linhas: a primeira linha data[0, :] é x e as linhas seguintes data[1:, :] são os sinais y. Cada linha tem comprimento igual ao número de amostras S.
      • Se sinais em colunas: a primeira coluna data[:, 0] é x e as colunas seguintes data[:, 1:] são os sinais y. Transpor se necessário para obter uma lista de sinais fácil de iterar.
      • Ensino rápido: em NumPy data.T devolve a transposta; também dá para rearranjar com np.vstack / np.column_stack.
    5. Perguntar ao usuário se quer plotar todos na mesma figura (opção 's') ou em figuras separadas ('n').
    6. Preparar limites dos eixos:
      • Para gráficos na mesma figura: calcular x_{\min}, x_{\max} a partir de x e y_{\min}, y_{\max} a partir de todos os y. Acrescentar uma margem percentual (por exemplo 5\%) para deixar o gráfico “respirar”.
      • Para gráficos separados: calcular limites individuais por sinal com a mesma margem.
      • Ensino rápido (fórmula de margem): se v_{\min} e v_{\max} são os extremos, definir \text{margem}=0{,}05\cdot(v_{\max}-v_{\min}) e usar [\;v_{\min}-\text{margem},\;v_{\max}+\text{margem}\;]. Se v_{\max}=v_{\min} (sinal constante) adicionar um epsilon pequeno, ex.: 0{,}01 ou 1\% do valor absoluto.
    7. Plotar com matplotlib:
      • Ensino rápido: plt.plot(x,y,label='...'), plt.xlabel(...), plt.ylabel(...), plt.legend(), plt.title(...), plt.xlim(...), plt.ylim(...), plt.grid(True), plt.show().
    8. Tratar erros e mensagens amigáveis para o usuário (arquivo mal formatado, dados NaN, etc.).

Passo 3

Detalhes práticos e explicações de funções principais que vou usar (aula curta sobre cada uma).
  • isfile:
    • Uso: from os.path import isfile e então isfile(filename).
    • Retorna True se o caminho existe e é um arquivo regular; False caso contrário.
  • np.genfromtxt / np.loadtxt:
    • np.genfromtxt(filename, delimiter=None, comments='#') tenta carregar dados numéricos de um arquivo texto. Se houver linhas/colunas com valores faltando, genfromtxt é mais tolerante e preenche com nan.
    • np.loadtxt é mais strict (erro em linhas de tamanho distinto).
  • Formato do array carregado:
    • Após carregar, data.shape retorna (n_{\text{rows}}, n_{\text{cols}}) para arrays 2D.
    • Indexação útil:
      • data[0, :] -> primeira linha (todas as colunas).
      • data[:, 0] -> primeira coluna (todas as linhas).
      • data[1:, :] -> todas as linhas a partir da segunda.
  • Matplotlib:
    • plt.figure() cria uma nova figura.
    • plt.plot(x, y, label=...) desenha curva.
    • plt.legend() mostra legenda com rótulos dos label.
    • plt.title(...), plt.xlabel(...), plt.ylabel(...).
    • plt.xlim(a, b) e plt.ylim(c, d) ajustam limites.
    • plt.tight_layout() melhora espaçamento.
    • plt.show() exibe tudo (bloqueante em modo script).
  • Boas práticas:
    • Converter arrays para float (ex.: data = data.astype(float)) para evitar problemas.
    • Tratar NaN com np.isnan e avisar o usuário.

Passo 4

Tratamento da detecção da orientação dos sinais com exemplos concretos.
  • Se data.shape == (10, 3) então n_{\text{rows}}=10 e n_{\text{cols}}=3. Como 10>3 significa que provavelmente há 10 amostras e 3 sinais (os sinais estão nas colunas). Assim a primeira coluna será x (comprimento 10) e as outras duas colunas são os y.
  • Se data.shape == (3, 100) então n_{\text{rows}}=3 e n_{\text{cols}}=100. Como 100>3 significa que provavelmente há 100 amostras e 3 sinais (os sinais estão nas linhas). Assim a primeira linha será x (comprimento 100) e as outras duas linhas são os y.
  • Caso n_{\text{rows}} == n_{\text{cols}} fiquei com empate (ex.: matriz quadrada). Aí o script não consegue inferir automaticamente e vai pedir ao usuário que escolha a orientação — única solução confiável sem informação externa.

Passo 5

Como vou montar os rótulos e títulos conforme pedido (detalhes de formatação).
  • Para legenda (mesma figura): nomes no formato 'sinal 01', 'sinal 02', …, sempre com dois dígitos. Em Python: f"sinal {i+1:02d}".
  • Para título (figuras separadas): 'Plotando o Sinal 01', 'Plotando o Sinal 02', etc. Em Python: f"Plotando o Sinal {i+1:02d}".
  • Labels dos eixos: usar plt.xlabel('Eixo x') e plt.ylabel('Amplitude'). Poderia parametrizar, mas essa é uma escolha clara e útil.

Passo 6

Decisão de implementação sobre limites dos eixos (detalhes para realizar um “ajuste harmônico”).
  • Cálculo da margem:
    • Para um conjunto de valores v (p.ex. todos os y de todos os sinais), calcular v_{\min}=\min(v) e v_{\max}=\max(v).
    • Calcular \text{margem}=0{,}05\cdot (v_{\max}-v_{\min}).
    • Se v_{\max} \approx v_{\min} (sinal constante), usar margem fixa, por exemplo epsilon = max(1e-3, 0.01*abs(v_max) ).
    • Usar limites [v_{\min}-\text{margem},\; v_{\max}+\text{margem}].
  • Aplicação:
    • Para todos na mesma figura: calcular x-lim e y-lim globalmente usando todos os sinais e usar esses limites para a figura inteira.
    • Para figuras separadas: calcular limites com base no x e em cada y individual.

Passo 7

Apresentação do script principal final (explicação por blocos) — depois coloco o código completo no campo de resposta final.
  • O script faz, em ordem:
    1. Importações (os, numpy as np, matplotlib.pyplot as plt, sys).
    2. Recebe o nome do arquivo via input().
    3. Testa existência com isfile.
    4. Carrega com np.genfromtxt.
    5. Verifica ndim e shape e decide orientação (ou pergunta se empate).
    6. Extrai x e a lista de sinais y.
    7. Pergunta ao usuário se quer todos na mesma figura.
    8. Calcula limites (com margem) e plota, formatando legendas/títulos/labels.
    9. Mostra o(s) gráfico(s) com plt.show().
  • Observações sobre robustez no código:
    • Se o arquivo contiver NaN, o script avisará e tentará plotar as partes válidas (matplotlib ignora NaN).
    • Se data.ndim == 1 e o usuário não souber como interpretar, o script aborta com instrução de como formatar o arquivo de teste (ou usar o script gerador que vou disponibilizar).

Passo 8

Script de geração de arquivos de teste (modificação pedida da AA1-Questão2) — explicação e o que ele faz.
  • Esse script gera:
    • Um eixo x com N amostras (por ex.: N=200 por padrão).
    • Três sinais de exemplo y_1, y_2, y_3 (ex.: seno, cosseno escalado, e uma parábola), suficientes para testar sobreposição e diferenças de amplitude.
    • Permite salvar os dados no arquivo em duas opções (escolha do usuário):
      • linhas: cada sinal ocupa uma linha (primeira linha = x, demais linhas = sinais).
      • colunas: cada sinal ocupa uma coluna (primeira coluna = x, demais colunas = sinais).
  • Por que isso é útil:
    • Permite criar .dat com sinais em linhas e em colunas para testar o script principal sem ter que editar manualmente.

Passo 9

Pontos finais, dicas de uso e testes rápidos (curtos, motivadores).
  • Para testar:
    1. Executar o script gerador e escolher linhas ou colunas. Ele criará, por exemplo, teste_linhas.dat ou teste_colunas.dat.
    2. Executar o script principal e passar o nome do arquivo gerado.
    3. Escolher se quer uma figura ou várias e observar os plots.
  • Dica de solução de problemas:
    • Se aparecer mensagem de erro dizendo que data é 1D, provavelmente o arquivo tem apenas uma linha ou apenas uma coluna sem separadores corretos; reabrir o arquivo e conferir separadores (espaço, tab). O gerador entregue não comete esse erro.
  • Força aí — dá para fazer isso sem pânico! Se ficar travado num erro, copie a mensagem de erro e me manda que eu te ajudo a ajustar o script.

Passo 10

Código final (os scripts solicitados). Estes são os scripts que podem ser salvos como arquivos .py e usados diretamente.
  • Script 1: script principal que lê .dat e plota (plot_dat.py).
  • Script 2: gerador de arquivos de teste (versão modificada da AA1-Questão2) (generate_dat_test.py).

Resposta

Segue o código dos dois scripts pedidos. Copie cada bloco para um arquivo .py distinto.
Script principal (salvar como plot_dat.py):
#!/usr/bin/env python3
# plot_dat.py
# Lê um arquivo .dat e plota sinais; detecta se sinais estão em linhas ou colunas.
import sys
from os.path import isfile
import numpy as np
import matplotlib.pyplot as plt

def ask_choice(prompt, choices):
    """Pergunta ao usuário até receber uma resposta válida presente em choices (lista de strings)."""
    choices_str = "/".join(choices)
    while True:
        resp = input(f"{prompt} ({choices_str}): ").strip().lower()
        if resp in choices:
            return resp
        print(f"Resposta inválida. Escolha {choices_str}.")

def compute_limits(vals, margin_frac=0.05):
    """Dado um array 1D de valores, retorna (min_lim, max_lim) com margem."""
    vmin = np.nanmin(vals)
    vmax = np.nanmax(vals)
    if np.isfinite(vmin) and np.isfinite(vmax):
        if np.isclose(vmax, vmin):
            # caso sinal constante: escolher margem relativa ou fixa pequena
            epsilon = max(1e-3, 0.01 * max(abs(vmax), 1.0))
            return (vmin - epsilon, vmax + epsilon)
        margin = margin_frac * (vmax - vmin)
        return (vmin - margin, vmax + margin)
    else:
        # fallback caso haja NaN em tudo
        return (-1.0, 1.0)

def main():
    filename = input("Digite o nome do arquivo .dat (ex: dados.dat): ").strip()
    if not isfile(filename):
        print(f"Arquivo '{filename}' não encontrado. Verifique o caminho e tente novamente.")
        sys.exit(1)

    # Tentar carregar o arquivo com genfromtxt (mais tolerante)
    try:
        data = np.genfromtxt(filename, delimiter=None, comments="#", autostrip=True)
    except Exception as e:
        print("Erro ao ler o arquivo com np.genfromtxt():", e)
        sys.exit(1)

    if data.size == 0:
        print("Arquivo vazio ou sem dados numéricos.")
        sys.exit(1)

    # Garantir float
    try:
        data = data.astype(float)
    except Exception as e:
        print("Os dados não são todos numéricos:", e)
        sys.exit(1)

    # Determinar forma e orientação
    if data.ndim == 1:
        # Resultado 1D: pode ser uma única linha ou uma única coluna.
        print("Os dados foram lidos como um vetor 1D. O arquivo pode ter apenas uma linha ou apenas uma coluna.")
        orient = ask_choice("Os sinais estão organizados em 'linhas' (cada linha = um sinal) ou 'colunas' (cada coluna = um sinal)?", ["linhas", "colunas"])
        if orient == "linhas":
            # transformar para 2D como uma única linha
            data = data[np.newaxis, :]
        else:
            # transformar para 2D como uma única coluna
            data = data[:, np.newaxis]

    n_rows, n_cols = data.shape
    # Decidir orientação automaticamente quando possível
    if n_cols > n_rows:
        orientation = "linhas"   # cada linha = um sinal, colunas = amostras
    elif n_rows > n_cols:
        orientation = "colunas"  # cada coluna = um sinal, linhas = amostras
    else:
        # empate: perguntar ao usuário
        print(f"Não foi possível inferir automaticamente a orientação: n_rows = {n_rows}, n_cols = {n_cols}")
        orientation = ask_choice("Informe a orientação dos sinais no arquivo ('linhas' ou 'colunas')", ["linhas", "colunas"])

    # Extrair x e sinais y
    if orientation == "linhas":
        # data.shape = (n_signals, n_samples)
        if n_rows < 2:
            print("Não há sinais suficientes: precisa existir pelo menos uma linha para x e outra para y.")
            sys.exit(1)
        x = data[0, :]
        ys = data[1:, :]  # shape = (n_signals-1, n_samples)
        # transformar para lista de sinais (cada sinal 1D com comprimento n_samples)
        signals = [ys[i, :] for i in range(ys.shape[0])]
    else:
        # orientation == "colunas"
        # data.shape = (n_samples, n_signals)
        if n_cols < 2:
            print("Não há sinais suficientes: precisa existir pelo menos uma coluna para x e outra para y.")
            sys.exit(1)
        x = data[:, 0]
        ys = data[:, 1:]  # shape = (n_samples, n_signals-1)
        # transformar para lista de sinais: transpor
        signals = [ys[:, j] for j in range(ys.shape[1])]

    # Remover sinais vazios/NaN constantemente inválidos (avisar)
    clean_signals = []
    for i, s in enumerate(signals):
        if np.all(np.isnan(s)):
            print(f"Aviso: o sinal {i+1} contém apenas NaN e será ignorado.")
            continue
        clean_signals.append(s)
    signals = clean_signals
    if len(signals) == 0:
        print("Nenhum sinal válido para plotar.")
        sys.exit(1)

    # Perguntar se quer plotar tudo na mesma figura
    escolha = ask_choice("Deseja plotar todos os sinais na mesma figura? (s/n)", ["s", "n"])

    # Preparar labels/títulos
    def nome_sinal(idx):
        return f"sinal {idx+1:02d}"

    # Plotagem
    if escolha == "s":
        # plotar todos juntos: calcular limites globais
        # x-limits
        try:
            xlim = compute_limits(x)
        except Exception:
            xlim = (np.nanmin(x), np.nanmax(x))
        # y-limits global
        all_y_values = np.hstack([np.asarray(s).ravel() for s in signals])
        ylim = compute_limits(all_y_values)
        plt.figure(figsize=(8, 5))
        for i, s in enumerate(signals):
            plt.plot(x, s, label=nome_sinal(i))
        plt.xlabel("Eixo x")
        plt.ylabel("Amplitude")
        plt.title("Sinais (mesma figura)")
        plt.legend(loc="best")
        plt.xlim(xlim)
        plt.ylim(ylim)
        plt.grid(True)
        plt.tight_layout()
        plt.show()
    else:
        # plotar em figuras separadas
        for i, s in enumerate(signals):
            plt.figure(figsize=(7, 4))
            plt.plot(x, s)
            plt.xlabel("Eixo x")
            plt.ylabel("Amplitude")
            plt.title(f"Plotando o Sinal {i+1:02d}")
            xlim = compute_limits(x)
            ylim = compute_limits(s)
            plt.xlim(xlim)
            plt.ylim(ylim)
            plt.grid(True)
            plt.tight_layout()
            plt.show()

    print("Plotagem concluída.")

if __name__ == "__main__":
    main()
Script gerador de arquivos de teste (salvar como generate_dat_test.py):
#!/usr/bin/env python3
# generate_dat_test.py
# Gera arquivos .dat de teste com sinais em linhas ou em colunas.
import numpy as np
from os.path import isfile

def ask_int(prompt, default):
    s = input(f"{prompt} [padrão {default}]: ").strip()
    if s == "":
        return default
    return int(s)

def ask_choice(prompt, choices):
    choices_str = "/".join(choices)
    while True:
        resp = input(f"{prompt} ({choices_str}): ").strip().lower()
        if resp in choices:
            return resp
        print(f"Resposta inválida. Escolha {choices_str}.")

def main():
    fname = input("Nome do arquivo de saída (ex: teste.dat): ").strip()
    if fname == "":
        print("Nome inválido.")
        return
    if isfile(fname):
        resp = ask_choice(f"O arquivo '{fname}' já existe. Deseja sobrescrever?", ["s", "n"])
        if resp == "n":
            print("Abortando para não sobrescrever.")
            return

    n_samples = ask_int("Número de amostras (N)", 200)
    # Criar eixo x e sinais exemplo
    x = np.linspace(0.0, 1.0, n_samples)
    # Três sinais de exemplo
    y1 = np.sin(2.0 * np.pi * 2.0 * x)             # seno baixa frequência
    y2 = 0.6 * np.cos(2.0 * np.pi * 5.0 * x)       # cosseno média frequência, amplitude menor
    y3 = (x - 0.5)**2 - 0.1                        # parábola deslocada
    # Coleção de sinais (primeiro será x)
    signals = [x, y1, y2, y3]

    orient = ask_choice("Deseja salvar em 'linhas' (cada linha = um sinal) ou 'colunas' (cada coluna = um sinal)?", ["linhas", "colunas"])

    if orient == "linhas":
        # empilhar como linhas: shape = (n_signals, n_samples)
        data = np.vstack(signals)
    else:
        # empilhar como colunas: shape = (n_samples, n_signals)
        data = np.column_stack(signals)

    # Salvar
    try:
        np.savetxt(fname, data, fmt="%.8f")
        print(f"Arquivo salvo em '{fname}'. Orientação: {orient}. shape dos dados: {data.shape}")
    except Exception as e:
        print("Erro ao salvar o arquivo:", e)

if __name__ == "__main__":
    main()

Exercícios relacionados