Skip to content

Fixing duplicate column name issue #1166 - #1470

Merged
VisLab merged 9 commits into
hed-standard:mainfrom
VisLab:duplicate_columns
Oct 19, 2026
Merged

VisLab merged 9 commits into
hed-standard:mainfrom
VisLab:duplicate_columns

Conversation

@VisLab

@VisLab VisLab commented Oct 19, 2026

Copy link
Copy Markdown
Member

No description provided.

hed-tests 80a17f0 is hed-standard main and adds TABULAR_INVALID.json, the repeated-column-name cases of issue hed-standard#1166.
A tabular file whose header repeats a column name (spec 3.2.10) now
gets one TABULAR_INVALID per repeated name, in order of its second
appearance, with ec_filename and ec_column. Validation stops there:
neither the sidecar nor any row is validated or assembled. Issue hed-standard#1166.

BaseInput reads the raw header line before pandas suffixes repeats
and exposes the names as duplicate_columns. Header cells are trimmed
and the trimmed names are the dataframe's column names, so a name
that differs only by whitespace is the same column and a repeat.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Quoted headers and invalid inherited BIDS sidecars can bypass duplicate-column reporting.

2 open issues
What changed in this PR

Addresses #1166 by detecting duplicate column names before HED annotation validation.

Changes:

  • Records repeated headers across text, Excel, and DataFrame inputs.
  • Adds an early TABULAR_INVALID check with column and file context.
  • Adds regression coverage and documents the behavior.
File Description
tests/​validator/​test_spreadsheet_validator.py Tests duplicate-header reporting and early stopping.
tests/​tools/​bids/​test_bids_dataset.py Tests duplicate columns in dataset validation.
tests/​models/​test_base_input.py Tests header detection across input formats.
tests/​data/​spreadsheet_validator_tests/​duplicate_columns.tsv Adds a duplicate-header fixture.
hed/​validator/​spreadsheet_validator.py Adds the initial header-validation stage.
hed/​models/​column_mapper.py Clarifies duplicate handling in comments.
hed/​models/​base_input.py Records duplicates and normalizes column names.
hed/​errors/​error_types.py Defines TABULAR_INVALID.
hed/​errors/​error_messages.py Adds the duplicate-column error message.
docs/​user_guide.md Documents header checks and duplicate tracking.

Review effort: Balanced

Comment thread hed/models/base_input.py Outdated
Comment thread hed/validator/spreadsheet_validator.py
The repeated-name check split the raw first line on tabs, so quoted
cells disagreed with the dataframe: trial and "trial" were not a
repeat, and a fully quoted repeat was reported with its quotes. Read
the first record with pandas (header=None, nrows=1) so quotes,
multiline cells, BOM and blank lines are handled as for the data.
validate_datafiles skipped a data file whose merged sidecar had
errors before the header check could run. Load it anyway, and
validate it when its header repeats a column name, so it reports
TABULAR_INVALID and no row is checked against the bad sidecar.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

BIDS discovery misses whitespace-normalized HED headers, and loading can fail for readable text streams positioned with next().

1 open issue
2 previously missed issues

In code that hasn't changed since last review

Medium severity Trim BIDS discovery labels before detecting HED columns

hed/​models/​base_input.py:511

BIDS discovery needs the same header trimming. For a sidecar-free events file with header HED \t HED, direct TabularInput validation now detects duplicate HED names. However, BidsFileGroup._make_datafile_dict checks untrimmed labels at lines 276-280, leaving has_hed=False, so BIDS validation skips the file without reporting TABULAR_INVALID. Trim the discovery labels before checking for HED columns, and add a sidecar-free BIDS regression for this header.

Medium severity Buffer text streams when tell() fails after next()

hed/​models/​base_input.py:537

A seekable text stream can still reject tell(). After next(file) skips a preamble, TextIOWrapper.tell() raises OSError: telling position disabled by next() call, although the remaining TSV is readable. This new call therefore makes loading fail with INVALID_FILE_FORMAT. Fall back to buffering when position reporting fails, and add a regression using TextIOWrapper positioned with next().

1 resolved since last review

Review effort: Balanced

_make_datafile_dict compared untrimmed header names, so a file with
no sidecar headed "HED \t HED" had has_hed False and was skipped,
although BaseInput trims names and reports the repeat. Trim them the
same way before looking for the HED column.
TextIOWrapper.tell() raises after next(), so reading the header
record failed for a stream advanced past a preamble, which loaded
before. Read such a stream into memory, as for one that cannot seek.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Loading malformed TSVs before the invalid-sidecar guard can abort validation instead of returning collected issues.

1 open issue
1 resolved since last review

Review effort: Balanced

Comment thread hed/tools/bids/bids_file_group.py
Since files with an invalid merged sidecar are loaded to look for a
repeated column name, one that cannot be loaded (a blank column name)
raised HedFileError and aborted the run, losing the issues collected.
On that path a load failure now skips the file, as before.
Repeated column names no longer raise; only blank names do.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

Header normalization breaks existing name-based selections, and cross-format loading changes still need runtime verification.

No open issues 🎉

1 previously missed issue

In code that hasn't changed since last review

Medium severity Normalize column selectors after trimming labels

hed/​models/​base_input.py:511

Trimming column labels without normalizing the mapper's selectors breaks existing name-based selections, even without duplicate headers. For example, a SpreadsheetInput column named ' HED tags ' selected with tag_columns=[' HED tags '] is renamed to 'HED tags', but ColumnMapper._convert_to_names() still matches the original selector literally. The column is omitted from assembly, and validation reports a missing required column. Prefix-dictionary keys have the same mismatch. Normalize string selectors consistently in mapping and required-column checks, including optional selectors, and add regressions for tag-column and prefix-dictionary selections.

1 resolved since last review

Review effort: Balanced

Column names are trimmed when a file is read, but ColumnMapper kept
tag_columns, optional_tag_columns and column_prefix_dictionary keys
as given, so a selector " HED tags " no longer matched its column and
validation reported a missing required column. Trim string selectors
where the mapper stores them.
@VisLab
VisLab merged commit eae6f62 into hed-standard:main Oct 19, 2026
18 checks passed
VisLab added a commit that referenced this pull request Oct 21, 2026
Column names and top-level sidecar keys are used exactly as read;
the column-name trimming of PR #1470 is removed. Implements
hed-specification 3.2.10 and Appendix B:

- A column name that begins or ends with blank, tab, LF or CR is
  TABULAR_INVALID (b); a blank name is TABULAR_INVALID (c). With
  repeated names (a), they are reported in the order b, c, a before
  the sidecar, and validation stops there.
- The check runs for any ColumnSource, on column_names(), whose
  docstring now says the names are exactly as the table holds them.
- A padded top-level sidecar key is SIDECAR_INVALID (d).
- A blank name no longer raises BAD_COLUMN_NAMES at load; the
  allow_blank_names switch, check_for_blank_names, the
  HED_BLANK_COLUMN warning and the "Unnamed: " prefix rule are
  removed. DataFrame labels are taken literally.
- BIDS validation still reports such a file when its merged sidecar
  has errors; discovery trims header names only to find HED.
@VisLab
VisLab deleted the duplicate_columns branch October 22, 2026 20:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants