<rss version="2.0">
  <channel>
    <title>Excel on LLBBL Blog</title>
    <link>https://llbbl.blog/categories/excel/</link>
    <description></description>
    
    <language>en</language>
    
    <lastBuildDate>Wed, 19 Aug 2026 10:00:00 -0500</lastBuildDate>
    
    <item>
      <title>Nobody Agrees What a CSV Is</title>
      <link>https://llbbl.blog/2026/08/19/nobody-agrees-what-a-csv.html</link>
      <pubDate>Wed, 19 Aug 2026 10:00:00 -0500</pubDate>
      
      <guid>http://llbbl.micro.blog/2026/08/19/nobody-agrees-what-a-csv.html</guid>
      <description>&lt;p&gt;CSV is simple but powerful. Values, separated by commas. It&amp;rsquo;s easy to understand and use.&lt;/p&gt;
&lt;p&gt;It is also, by a wide margin, the one that destroys the most data.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s not a paradox. It&amp;rsquo;s cause and effect. A format simple enough that everyone writes their own parser is a format with as many dialects as it has parsers, and CSV&amp;rsquo;s defining property is that it carries no information about how to read it.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;there-is-no-standard&#34;&gt;There Is No Standard&lt;/h2&gt;
&lt;p&gt;RFC 4180 exists. Yakov Shafranovich published it in October 2005, it registers the &lt;code&gt;text/csv&lt;/code&gt; media type, and it gives an ABNF grammar.&lt;/p&gt;
&lt;p&gt;It is also &lt;strong&gt;Informational&lt;/strong&gt;, not Standards Track. It was written to describe what people were already doing, twenty-odd years after spreadsheets started emitting it. Section 2 says so outright: there is &amp;ldquo;no formal specification in existence,&amp;rdquo; and what follows documents &amp;ldquo;the format that seems to be followed by most implementations.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;By the time someone wrote it down, every spreadsheet, database, and scripting language had already shipped its own interpretation. The RFC didn&amp;rsquo;t settle anything. It just added one more dialect, with the distinction of having a number.&lt;/p&gt;
&lt;p&gt;Eight years later, RFC 7111 added URI fragments for pointing at a row, column, or cell inside a &lt;code&gt;text/csv&lt;/code&gt; file. Also Informational. CSV still had no standard, but you could now cite a specific cell of one.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;where-does-a-record-end&#34;&gt;Where Does a Record End?&lt;/h2&gt;
&lt;p&gt;The obvious answer is &amp;ldquo;at the newline,&amp;rdquo; and the obvious answer is wrong, because a quoted field is allowed to contain one.&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;name,notes
Alice,&amp;#34;line one
line two&amp;#34;
Bob,fine
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That&amp;rsquo;s a valid three-row CSV. Split it on newlines and you get four:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;naive split gives 4 lines:
   &amp;#39;name,notes&amp;#39;
   &amp;#39;Alice,&amp;#34;line one&amp;#39;
   &amp;#39;line two&amp;#34;&amp;#39;
   &amp;#39;Bob,fine&amp;#39;

a real CSV parser gives 3 rows:
   [&amp;#39;name&amp;#39;, &amp;#39;notes&amp;#39;]
   [&amp;#39;Alice&amp;#39;, &amp;#39;line one\nline two&amp;#39;]
   [&amp;#39;Bob&amp;#39;, &amp;#39;fine&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Every &lt;code&gt;head&lt;/code&gt;, &lt;code&gt;wc -l&lt;/code&gt;, &lt;code&gt;split(&amp;quot;\n&amp;quot;)&lt;/code&gt;, and shell pipeline that assumes one record per line is wrong on this file. Not wrong on a malformed file. Wrong on a correct one.&lt;/p&gt;
&lt;p&gt;This is the single most common CSV bug, and it&amp;rsquo;s invisible in testing, because your test fixtures don&amp;rsquo;t have newlines in them until a user pastes an address into a form.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;whats-the-delimiter&#34;&gt;What&amp;rsquo;s the Delimiter?&lt;/h2&gt;
&lt;p&gt;In most of Europe the decimal separator is a comma. &lt;code&gt;12,50&lt;/code&gt; is twelve and a half euros. Which means a comma cannot also be a field separator, so those locales use semicolons.&lt;/p&gt;
&lt;p&gt;Feed a German CSV to an RFC 4180 parser and everything survives, in the sense that nothing throws:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;input:  produkt;preis
        Kaffee;12,50
        Tee;9,90

parsed as comma-delimited:
   [&amp;#39;produkt;preis&amp;#39;]
   [&amp;#39;Kaffee;12&amp;#39;, &amp;#39;50&amp;#39;]
   [&amp;#39;Tee;9&amp;#39;, &amp;#39;90&amp;#39;]

parsed as semicolon-delimited:
   [&amp;#39;produkt&amp;#39;, &amp;#39;preis&amp;#39;]
   [&amp;#39;Kaffee&amp;#39;, &amp;#39;12,50&amp;#39;]
   [&amp;#39;Tee&amp;#39;, &amp;#39;9,90&amp;#39;]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The first reading gives you two columns of nonsense with no error. The prices split down the middle of the decimal point. A pipeline that ingests this will happily compute statistics on the number 12 and the number 50.&lt;/p&gt;
&lt;p&gt;Excel picks the delimiter based on your operating system&amp;rsquo;s regional settings, which means the same file opens differently on two machines in the same office.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;is-the-first-row-a-header&#34;&gt;Is the First Row a Header?&lt;/h2&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;1,2,3
4,5,6
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Header or data? Nothing in the file says.&lt;/p&gt;
&lt;p&gt;RFC 4180&amp;rsquo;s answer is that you put it in the MIME type: &lt;code&gt;text/csv; header=present&lt;/code&gt;. Which is a real answer, and also means the information lives outside the file, in a transport layer that gets stripped the moment someone saves the attachment to disk.&lt;/p&gt;
&lt;p&gt;So in practice every tool guesses, usually by checking whether the first row looks less numeric than the rest.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-part-that-destroys-data&#34;&gt;The Part That Destroys Data&lt;/h2&gt;
&lt;p&gt;Everything above is a parsing problem. This one is worse, because the file parses fine and the damage happens after.&lt;/p&gt;
&lt;p&gt;CSV has no types. Every value is text. So every spreadsheet and dataframe library applies type inference on import, and type inference is lossy.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s a file with four columns of identifiers, all of which are strings that happen to be made of digits:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;gene,zip,card,accession
SEPT7,02138,4532012345678901,0004928
MARCH1,01234,4111111111111111,0000071
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Read it with a type-inferring reader and:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;  gene  zip             card  accession
 SEPT7 2138 4532012345678901       4928
MARCH1 1234 4111111111111111         71
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The ZIP code &lt;code&gt;02138&lt;/code&gt; is now &lt;code&gt;2138&lt;/code&gt;. The accession number &lt;code&gt;0004928&lt;/code&gt; is now &lt;code&gt;4928&lt;/code&gt;. Nobody was asked. Nothing warned. Save that back to CSV and the original values are gone from disk.&lt;/p&gt;
&lt;p&gt;Spreadsheets are worse than this, because they store every number as an IEEE 754 double. Microsoft is blunt about the consequence:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Excel has a maximum precision of &lt;strong&gt;15 significant digits&lt;/strong&gt;, which means that for any number containing 16 or more digits, such as a credit card number, any numbers past the 15th digit are rounded down to zero.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The example they reach for is a card number:&lt;/p&gt;
&lt;pre tabindex=&#34;0&#34;&gt;&lt;code&gt;typed into a cell     1234 5678 9087 6543
Excel shows           1.23E+15
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Microsoft calls that &amp;ldquo;truncating numerical data to 15 digits of precision and converting to a number displayed in scientific notation.&amp;rdquo; Note that this is the vendor describing its own product, not a bug report.&lt;/p&gt;
&lt;p&gt;The card number in the file above is also 16 digits. pandas read it back intact. Excel would not.&lt;/p&gt;
&lt;p&gt;Credit card numbers are 16 digits. Many national ID numbers are longer. They are not numbers in any meaningful sense, they&amp;rsquo;re strings of digits, and a format with no type information cannot tell the difference.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;the-gene-name-problem&#34;&gt;The Gene Name Problem&lt;/h2&gt;
&lt;p&gt;The best-documented case of this is genomics, because biologists name genes things like &lt;code&gt;SEPT1&lt;/code&gt; and &lt;code&gt;MARCH1&lt;/code&gt; and spreadsheets read those as dates.&lt;/p&gt;
&lt;p&gt;In 2016 Ziemann and colleagues screened 35,175 supplementary Excel files from 18 journals covering 2005 to 2015. Among articles containing Excel gene lists, &lt;strong&gt;19.6%&lt;/strong&gt; had gene names corrupted this way. One in five.&lt;/p&gt;
&lt;p&gt;A follow-up in 2021, &amp;ldquo;Gene name errors: Lessons not learned,&amp;rdquo; found &lt;strong&gt;30.9%&lt;/strong&gt; across a broader sample drawn from PubMed Central. Worth being careful comparing those two numbers directly, because the second study used a different sampling frame and also detected an additional error category the first one didn&amp;rsquo;t look for. The honest summary is that the problem did not go away in the five years after being loudly published.&lt;/p&gt;
&lt;p&gt;The resolution is the remarkable part. The field did not fix the spreadsheets. It &lt;strong&gt;renamed the genes&lt;/strong&gt;. The HUGO Gene Nomenclature Committee&amp;rsquo;s 2020 guidelines state that &amp;ldquo;all symbols that auto-converted to dates in Microsoft Excel have been changed,&amp;rdquo; giving &lt;code&gt;SEPT1&lt;/code&gt; becoming &lt;code&gt;SEPTIN1&lt;/code&gt; and &lt;code&gt;MARCH1&lt;/code&gt; becoming &lt;code&gt;MARCHF1&lt;/code&gt; as examples.&lt;/p&gt;
&lt;p&gt;Human genes were renamed because a file format cannot say what type a column is.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&#34;what-to-do-about-it&#34;&gt;What To Do About It&lt;/h2&gt;
&lt;p&gt;CSV isn&amp;rsquo;t going away, and mostly shouldn&amp;rsquo;t. It&amp;rsquo;s readable, streamable, diffable, and every tool on earth reads it.&lt;/p&gt;
&lt;p&gt;The practical defenses are short:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Quote everything.&lt;/strong&gt; It&amp;rsquo;s never wrong and it removes a whole class of ambiguity.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treat identifiers as strings explicitly&lt;/strong&gt; at the point of import. Every serious CSV reader lets you pin column types; use it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never round-trip through a spreadsheet&lt;/strong&gt; if the data contains identifiers. Opening and saving is a lossy operation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Say what you mean out of band.&lt;/strong&gt; Delimiter, encoding, header presence, quoting style. The file will not.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use something else when you can.&lt;/strong&gt; Parquet and even JSON Lines carry types. If the consumer is a program rather than a person, the readability argument for CSV mostly evaporates.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lesson generalizes past CSV, and it&amp;rsquo;s the same one from the text file post. A format that carries no description of itself pushes that burden onto every reader, and readers guess. Usually well. Occasionally by silently deleting the leading zero from your ZIP code.&lt;/p&gt;
&lt;h2 id=&#34;sources&#34;&gt;Sources&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc4180&#34;&gt;RFC 4180&lt;/a&gt; — the Informational spec that documents CSV rather than defining it&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://datatracker.ietf.org/doc/html/rfc7111&#34;&gt;RFC 7111&lt;/a&gt; — URI fragment selectors for &lt;code&gt;text/csv&lt;/code&gt;, January 2014&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://doi.org/10.1186/s13059-016-1044-7&#34;&gt;Ziemann et al., &amp;ldquo;Gene name errors are widespread in the scientific literature&amp;rdquo;&lt;/a&gt; — Genome Biology, 2016; the 19.6% figure (&lt;a href=&#34;https://pmc.ncbi.nlm.nih.gov/articles/PMC4994289/&#34;&gt;free full text&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://doi.org/10.1371/journal.pcbi.1008984&#34;&gt;Abeysooriya et al., &amp;ldquo;Gene name errors: Lessons not learned&amp;rdquo;&lt;/a&gt; — PLOS Computational Biology, 2021; the 30.9% follow-up&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://doi.org/10.1038/s41588-020-0669-3&#34;&gt;Bruford et al., &amp;ldquo;Guidelines for human gene nomenclature&amp;rdquo;&lt;/a&gt; — Nature Genetics, 2020; the renaming (&lt;a href=&#34;https://pmc.ncbi.nlm.nih.gov/articles/PMC7494048/&#34;&gt;free full text&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://learn.microsoft.com/en-us/troubleshoot/microsoft-365-apps/excel/floating-point-arithmetic-inaccurate-result&#34;&gt;Microsoft on Excel&amp;rsquo;s floating-point precision&lt;/a&gt; — Excel follows IEEE 754 and stores 15 digits of precision&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://support.microsoft.com/en-US/Excel/keeping-leading-zeros-and-large-numbers&#34;&gt;Microsoft on leading zeros and large numbers&lt;/a&gt; — &amp;ldquo;any numbers past the 15th digit are rounded down to zero,&amp;rdquo; with a credit card as the example&lt;/li&gt;
&lt;li&gt;&lt;a href=&#34;https://support.microsoft.com/en-US/Excel/get-started/import-or-export-text-txt-or-csv-files&#34;&gt;Microsoft on importing and exporting text files&lt;/a&gt; — the CSV list separator comes from Windows Region settings&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;I&amp;rsquo;d appreciate a follow. You can subscribe with your email below. The emails go out once a week, or you can find me on Mastodon at &lt;a href=&#34;https://micro.blog/llbbl?remote_follow=1&#34;&gt;@logan@llbbl.blog&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
</description>
    </item>
    
  </channel>
</rss>