Skip to content

Parse ARFF data records according to ARFF quoting rules - #532

Open
officialasishkumar wants to merge 1 commit into
RubixML:masterfrom
officialasishkumar:fix-arff-data-parsing
Open

officialasishkumar wants to merge 1 commit into
RubixML:masterfrom
officialasishkumar:fix-arff-data-parsing

Conversation

@officialasishkumar

Copy link
Copy Markdown

Fixes #530

The ARFF extractor used str_getcsv() with a single-quote enclosure to split data records. That doesn't follow ARFF's tokenizing rules, so valid files were imported incorrectly or rejected:

  • Whitespace around values was kept. 1.5, x produced " x", which doesn't match the nominal value x, and " ?" wasn't treated as missing.
  • Double-quoted values and attribute names weren't supported. Weka accepts them and tools such as R's write.arff produce them.
  • Backslash escapes in quoted data values were left in place ('don\'t' became don\'t), even though attribute names were already decoded correctly.
  • A quoted '?' was treated as missing. In ARFF only an unquoted ? means missing.
  • An apostrophe inside an unquoted value, such as don't, was taken as the start of a quoted string.

Changes

  • A new values() method splits records on commas and ignores surrounding whitespace. It unquotes single- or double-quoted values and returns an unquoted ? as null, so only real missing values are replaced with NAN or the placeholder.
  • Quoted-string parsing is moved into a shared quoted() helper, so attribute names and data values follow the same rules (backslash escapes, doubled quote characters).
  • opensQuote(): in balanced(), stripComment() and values(), a quote character only opens a string at the start of a token. As before, a stray " in the middle of a value (e.g. 5" tall) is a literal character.
  • The docs note now says the missing-value marker is an unquoted ?.

The existing extractEscapedApostrophe* tests expected the backslash to be kept (don\'t). They now expect don't, which matches how the same token is already decoded in attribute names.

Performance

balanced() and stripComment() run on every line. They now use strcspn() to skip runs of ordinary characters, and values() has a fast path for records without quotes. On a 100k-row file (best of 6 runs):

Records master this branch
unquoted 0.41s 0.08s
with quoted strings 0.49s 0.23s

For both benchmark files and tests/test.arff, the output is identical to master.

Tests

New tests cover whitespace around values, double-quoted values and attribute names (with commas, %, apostrophes and doubled quotes inside), quoted versus unquoted ?, and text after a closing quote, which raises an exception. The full suite, PHPStan and the coding style check pass.

The ARFF extractor split data records with str_getcsv() using a single
quote enclosure, which does not match how ARFF tokenizes values:

- Whitespace around values was retained, so " x" did not match the
  nominal value "x" and " ?" was not recognized as missing (numeric
  columns threw "Expected numeric value").
- Double-quoted values and attribute names, which Weka accepts and
  tools such as R's write.arff produce, were not understood. Commas
  and percent signs inside double quotes split the record or were
  stripped as comments.
- Backslash escapes in quoted data values were left in place
  ('don\'t' became don\'t) even though the same token in an attribute
  name was decoded to don't.
- A quoted '?' was treated as a missing value although only an
  unquoted ? denotes a missing value in ARFF.
- An apostrophe inside an unquoted value (don't) was taken as the
  start of a quoted string, causing following lines to be swallowed
  until an "Unterminated quoted string" error.

Data records are now split by a dedicated values() method that ignores
whitespace around values, unquotes single- or double-quoted values
using the same rules as attribute names, and returns unquoted ? as
null so that only genuine missing values are substituted. Quoted
string parsing is shared between attribute names and data values via
a new quoted() helper, and a quote only opens a quoted string at the
start of a token in balanced(), stripComment() and values().

balanced() and stripComment() now skip over runs of ordinary
characters with strcspn() and values() has a fast path for records
without quotes, which makes extraction faster than before (about 5x
on unquoted records and 2x on quoted records in a 100k row benchmark)
while producing identical output for previously supported files.

Fixes RubixML#530

This branch has not been deployed

No deployments
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.

ARFF extractor mis-parses whitespace, double quotes, escapes and quoted '?' in data records

1 participant