Pesquisa e fontes

Crie um dicionário de dados com significados, unidades, tipos e valores permitidos

Documente o significado de cada coluna do conjunto de dados antes da análise, incluindo unidade, tipo de dado e valores permitidos. Um pequeno conjunto de dados sintético mostra como o mesmo dicionário também pode servir para uma validação automática simples.

Ver o sumário

A tradução foi feita com IA. Confira o código, as unidades e os valores junto com o original. A revisão por falantes nativos de cada idioma ainda não foi concluída. English

Para quem éEste guia é voltado a pesquisadores que precisam de uma descrição clara, coluna por coluna, de um conjunto de dados que outras pessoas possam ler e verificar.

Preparação
  • Python 3.12 e um comando de terminal que inicie essa versão.
  • Um editor de texto ou programa de planilhas capaz de salvar arquivos CSV em UTF-8.
  • Uma pasta de trabalho em que o script possa criar uma nova pasta dentro de outputs.
  • É necessária apenas a biblioteca padrão do Python: csv, pathlib e re.

01Trate o dicionário de dados como parte do conjunto de dados

O nome de uma coluna raramente é suficiente para descrever dados de pesquisa. speed pode significar velocidade do veículo, velocidade da roda ou velocidade angular. accel pode significar aceleração longitudinal, lateral ou total. Um dicionário de dados registra o significado pretendido para que análises posteriores não dependam de suposições.

Este tutorial registra seis informações para cada coluna: nome da coluna, significado, unidade, tipo de dado, valores permitidos e um exemplo. O campo de valores permitidos pode descrever categorias, intervalos numéricos, regras de unicidade ou padrões simples de texto.

02Crie um pequeno conjunto de dados sintético de pesquisa

O conjunto de dados a seguir é sintético e foi criado especificamente para este artigo. Salve-o como test_runs.csv. Quatro linhas seguem as regras pretendidas. A quinta linha contém deliberadamente quatro problemas para que o dicionário possa ser usado como referência de validação.

csv
run_id,speed_kmh,accel_m_s2,road_surface,test_status
R001,30,0.5,dry,complete
R002,50,-1.2,wet,complete
R003,70,0.0,dry,review
R004,40,-0.8,wet,complete
R005,135,fast,snow,done

Há 5 linhas e 5 colunas. R005 tem um speed acima do intervalo permitido, um valor de acceleration não numérico, uma categoria de road-surface não suportada e um valor de test-status não suportado. O próprio run_id é válido.

03Escreva manualmente o dicionário de dados esperado

O dicionário deve ser compreensível sem abrir o código de análise. Mantenha o significado específico o suficiente para que outro pesquisador consiga distinguir a coluna de medições semelhantes.

column_namesignificadounidadetipovalores permitidosexemplo
run_idIdentificador exclusivo de uma execução de testestringR seguido de exatamente 3 dígitos; exclusivoR001
speed_kmhVelocidade do veículo no início do registrokm/hinteiro0 a 120 inclusive50
accel_m_s2Aceleração longitudinalm/s^2ponto flutuante-5.0 a 5.0 inclusive-1.2
road_surfaceCondição da superfície da via usada no testecategoriadry;wetwet
test_statusEstado de revisão do registro de testecategoriacomplete;reviewcomplete

Uma unidade vazia é intencional para identificadores e rótulos categóricos porque eles são metadados adimensionais, e não grandezas físicas medidas. O campo type descreve a representação de dados pretendida, e não o que uma planilha por acaso infere automaticamente.

04Gere o dicionário e valide o conjunto de dados

Salve o script a seguir como data_dictionary_check.py. A especificação das colunas é escrita uma única vez em SPEC. O script exporta essa especificação como data_dictionary.csv e verifica cada linha usando as mesmas regras. O arquivo original test_runs.csv é apenas lido.

python
import csv
import re
from pathlib import Path

SOURCE = Path("test_runs.csv")
OUTPUT_DIR = Path("outputs") / "data_dictionary_result"
DICTIONARY = OUTPUT_DIR / "data_dictionary.csv"
ISSUES = OUTPUT_DIR / "validation_issues.csv"

SPEC = [
    {
        "column_name": "run_id",
        "meaning": "Unique identifier for one test run",
        "unit": "",
        "type": "string",
        "allowed_values": "R followed by exactly 3 digits; unique",
        "example": "R001",
    },
    {
        "column_name": "speed_kmh",
        "meaning": "Vehicle speed at the start of the record",
        "unit": "km/h",
        "type": "integer",
        "allowed_values": "0 to 120 inclusive",
        "example": "50",
    },
    {
        "column_name": "accel_m_s2",
        "meaning": "Longitudinal acceleration",
        "unit": "m/s^2",
        "type": "float",
        "allowed_values": "-5.0 to 5.0 inclusive",
        "example": "-1.2",
    },
    {
        "column_name": "road_surface",
        "meaning": "Road-surface condition used for the test",
        "unit": "",
        "type": "category",
        "allowed_values": "dry;wet",
        "example": "wet",
    },
    {
        "column_name": "test_status",
        "meaning": "Review state of the test record",
        "unit": "",
        "type": "category",
        "allowed_values": "complete;review",
        "example": "complete",
    },
]


def issue(row_number, column, value, problem):
    return {
        "row_number": row_number,
        "column_name": column,
        "value": value,
        "issue": problem,
    }


def main() -> None:
    if not SOURCE.is_file():
        raise FileNotFoundError(f"Source CSV not found: {SOURCE}")
    if OUTPUT_DIR.exists():
        raise FileExistsError(f"Output folder already exists: {OUTPUT_DIR}")

    expected_columns = [item["column_name"] for item in SPEC]
    issues = []
    seen_run_ids = set()
    row_count = 0

    with SOURCE.open("r", encoding="utf-8-sig", newline="") as stream:
        reader = csv.DictReader(stream)
        if reader.fieldnames != expected_columns:
            raise ValueError(
                f"Header mismatch. Expected {expected_columns}, got {reader.fieldnames}."
            )

        for row_number, row in enumerate(reader, start=2):
            row_count += 1

            run_id = row["run_id"].strip()
            if re.fullmatch(r"R[0-9]{3}", run_id) is None:
                issues.append(issue(row_number, "run_id", run_id, "PATTERN_ERROR"))
            elif run_id in seen_run_ids:
                issues.append(issue(row_number, "run_id", run_id, "DUPLICATE_VALUE"))
            seen_run_ids.add(run_id)

            try:
                speed = int(row["speed_kmh"])
            except ValueError:
                issues.append(issue(row_number, "speed_kmh", row["speed_kmh"], "TYPE_ERROR"))
            else:
                if not 0 <= speed <= 120:
                    issues.append(issue(row_number, "speed_kmh", row["speed_kmh"], "OUT_OF_RANGE"))

            try:
                accel = float(row["accel_m_s2"])
            except ValueError:
                issues.append(issue(row_number, "accel_m_s2", row["accel_m_s2"], "TYPE_ERROR"))
            else:
                if not -5.0 <= accel <= 5.0:
                    issues.append(issue(row_number, "accel_m_s2", row["accel_m_s2"], "OUT_OF_RANGE"))

            if row["road_surface"] not in {"dry", "wet"}:
                issues.append(issue(row_number, "road_surface", row["road_surface"], "ALLOWED_VALUE_ERROR"))

            if row["test_status"] not in {"complete", "review"}:
                issues.append(issue(row_number, "test_status", row["test_status"], "ALLOWED_VALUE_ERROR"))

    OUTPUT_DIR.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_DIR.mkdir()

    dictionary_fields = [
        "column_name", "meaning", "unit", "type", "allowed_values", "example"
    ]
    with DICTIONARY.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=dictionary_fields)
        writer.writeheader()
        writer.writerows(SPEC)

    issue_fields = ["row_number", "column_name", "value", "issue"]
    with ISSUES.open("x", encoding="utf-8", newline="") as stream:
        writer = csv.DictWriter(stream, fieldnames=issue_fields)
        writer.writeheader()
        writer.writerows(issues)

    print(f"Rows checked: {row_count}.")
    print(f"Dictionary columns: {len(SPEC)}.")
    print(f"Validation issues: {len(issues)}.")
    print(f"Output folder: {OUTPUT_DIR.as_posix()}")


if __name__ == "__main__":
    main()

05Compare os problemas de validação esperados

Apenas R005 deve gerar problemas. Como o cabeçalho do CSV está na linha 1, R005 corresponde fisicamente à linha 6 do CSV. Quatro linhas de problemas devem ser gravadas.

csv
row_number,column_name,value,issue
6,speed_kmh,135,OUT_OF_RANGE
6,accel_m_s2,fast,TYPE_ERROR
6,road_surface,snow,ALLOWED_VALUE_ERROR
6,test_status,done,ALLOWED_VALUE_ERROR

A saída esperada do console abaixo foi derivada manualmente a partir do conjunto de dados sintético e do script. Não é um log capturado de execução.

text
Rows checked: 5.
Dictionary columns: 5.
Validation issues: 4.
Output folder: outputs/data_dictionary_result

06Verifique se o dicionário e os dados continuam alinhados

  • Confirme que cada coluna do conjunto de dados tenha exatamente uma linha correspondente no dicionário.
  • Verifique se as unidades físicas estão explícitas para as grandezas medidas.
  • Confirme que os valores categóricos permitidos usam a mesma grafia e capitalização do conjunto de dados.
  • Revise cada problema de validação em vez de excluir automaticamente as linhas inválidas.
  • Execute o script novamente sem alterar OUTPUT_DIR. Ele deve parar com FileExistsError em vez de substituir o dicionário e o relatório anteriores.
ProblemaO que verificar
Uma nova coluna do conjunto de dados não tem entrada no dicionárioAtualize o dicionário antes da análise para documentar o significado e a unidade.
O mesmo conceito aparece com unidades diferentesUse colunas separadas ou padronize explicitamente as unidades; não dependa da memória.
Os rótulos de categoria mudam ao longo do tempoDefina os valores permitidos e documente adições intencionais, como uma nova condição de via.
A planilha transforma IDs inteiros em númerosArmazene os identificadores como strings quando zeros à esquerda ou padrões forem significativos.
A validação passa, mas o significado está erradoVerificações estruturais não conseguem detectar uma definição científica incorreta ou um canal de sensor rotulado incorretamente.

07Expanda o dicionário conforme o projeto crescer

Um dicionário prático de dados de pesquisa frequentemente precisa de mais do que os seis campos mostrados aqui. Dependendo do projeto, adicione source system, sensor location, coordinate direction, sampling rate, nullable status, precision, calculation method, valid range, missing-value code e responsible owner.

Os valores permitidos devem refletir o significado científico, e não simplesmente o mínimo e o máximo observados no momento. Se os valores de speed em um arquivo estiverem entre 30 e 70 km/h, isso não significa automaticamente que 30 a 70 seja o intervalo de engenharia válido.

Versione o dicionário junto com o conjunto de dados ou o código de análise. Se a definição de uma coluna, unidade, convenção de sinal ou categoria mudar, registre quando a alteração ocorreu. Um dicionário de dados é mais útil quando descreve exatamente a versão dos dados que está sendo analisada.

Registro de execução e verificação

ChatGPT Python tool · Python 3.13.5 · standard library: csv, pathlib, re

  • A execução foi concluída com sucesso para 5 linhas e 5 colunas do dicionário.
  • O validation_issues.csv gerado continha exatamente 4 linhas, todas referentes à linha física 6 do CSV (R005): speed_kmh 135 OUT_OF_RANGE, accel_m_s2 fast TYPE_ERROR, road_surface snow ALLOWED_VALUE_ERROR e test_status done ALLOWED_VALUE_ERROR.
  • O validation_issues.csv gerado correspondeu exatamente ao CSV esperado do artigo.
  • O data_dictionary.csv gerado continha exatamente as 5 linhas SPEC e os valores de metadados esperados.
  • A saída do console correspondeu exatamente: Rows checked: 5; Dictionary columns: 5; Validation issues: 4; Output folder: outputs/data_dictionary_result.
  • A segunda execução gerou FileExistsError para outputs/data_dictionary_result, confirmando a proteção contra sobrescrita.
  • A comparação SHA-256 confirmou que test_runs.csv permaneceu inalterado.
Limites da verificação
  • O ambiente de execução era Python 3.13.5 em vez do Python 3.12 alvo do artigo.
  • O ambiente de execução emitiu no stderr um aviso não relacionado de aquecimento do ambiente de planilhas durante a inicialização do Python; isso não afetou a primeira execução bem-sucedida do script, os arquivos gerados nem o teste de proteção contra sobrescrita.
  • O script verifica apenas as regras declaradas de estrutura, tipo, intervalo e categoria; ele não valida a correção científica das próprias definições de metadados.

Fontes de referência

As explicações e os exemplos são de elaboração própria. Os comportamentos e conceitos relacionados podem ser consultados nas fontes oficiais abaixo.