1.3.4 CSV files
glmnet/data/csv reads a CSV file into a table and writes a table to one. It depends on nothing beyond Racket’s base package:
> (define text "site,age,response\nBoston,34,3.1\n\"Portland, ME\",51,5.2\n") > (define t (csv->table (open-input-string text))) > t
'(("site" . #("Boston" "Portland, ME"))
("age" . #(34.0 51.0))
("response" . #(3.1 5.2)))
> (table->csv t)
site,age,response
Boston,34,3.1
"Portland, ME",51,5.2
The first line is the header, which names the columns. A cell in quotes can hold commas, newlines, and quotes written twice (""), as RFC 4180 has it. Each cell is read as R’s read.csv reads a column that holds only that cell:
a number becomes a flonum: a decimal number, a hexadecimal one such as 0x1A, or Inf, Infinity or NaN in any case, with white space around it or not;
TRUE and T become #t, and FALSE and F become #f;
anything else stays a string, white space and all, so a column of strings works as a factor.
> (csv->table (open-input-string "x\n0x1A\n-inf\n 7 \nT\ntrue\n\" TRUE\"\n")) '(("x" . #(26.0 -inf.0 7.0 #t "true" " TRUE")))
Unlike R’s read.csv, which gives a whole column one type, each cell is read on its own. A column that mixes numbers and strings stays mixed, and the formula front end then raises an error naming the column, rather than reading the numbers as categories.
The input must be UTF-8. A file in another encoding, such as the Windows-1252 that Excel writes on Windows, is an error that names the line and the byte where it stops being UTF-8, rather than strings with characters replaced.
A missing cell, an empty one, one of white space or R’s NA, is an error that names the column, the row and the line of the file; nothing is dropped or filled in:
> (csv->table (open-input-string "x,y\n1,2\n3,\n")) csv->table: the input has a missing value
column: "y"
row: 1
line: 3
table->csv writes a flonum as the shortest decimal that reads back as the same flonum, so numbers round-trip exactly. It quotes a string when the file needs it, and a string that would read back as a number, such as "42", is quoted too, as R quotes every string; it still reads back as the number, as it does in R. csv-file->table and table->csv-file do the same with a file:
> (require racket/file) > (define file (make-temporary-file "patients-~a.csv")) > (table->csv-file patients file #:exists 'replace) > (display (file->string file))
age,site,dose,response
34,Boston,2.5,3.1
51,Portland,5,5.2
67,Portland,1,1.9
45,Boston,3.5,4
> (csv-file->table file)
'(("age" . #(34.0 51.0 67.0 45.0))
("site" . #("Boston" "Portland" "Portland" "Boston"))
("dose" . #(2.5 5.0 1.0 3.5))
("response" . #(3.1 5.2 1.9 4.0)))
csv->table reads the whole input into memory, and each column is a vector. For large files, Polars, which reads CSV files in native code, is faster (see Polars dataframes).