Werden wir Helden für einen Tag

Home | About | Archive

Projekt 71

Posted on Dec 26, 2022 by Chung-hong Chan


The package author (singular) tried very hard to write some C++ to achieve extremely little

“I hate new year resolutions” I said that two years ago before Corona.

In the last two years, I have written 6 OKR posts. And I think I have archived most, but not all of my five objectives (“Think out loud”, “Contribute to open source”, “Publish top papers”, “Improve myself”, “Publish a book”) set in Dec 2019. And because of my new job, some of the original OKRs are not as relevant as in Dec 2019. Some of them I will keep, some I wanted to change.

Among the five, I will keep “Think out loud”, “Contribute to open source”, “Publish top papers”. These are the things that I enjoy doing and should be continued. But I really wanted to change the other two objectives: “Improve myself” and “Publish a book”, for different reasons.

For “Improve myself”, I found it too ambiguous. The question always is “on what?” And when I did the past OKRs, I found some little improvements and claimed as a win. But that was not the original intent of the objective. There is no measurable key results, unlike “Think” by number of blog posts, “Contribute” by commits or R package shipped, and “Publish” by number of top papers published.

For “Publish a book”, I found that I no longer have passion about writing an automated content analysis textbook. One of the many reasons is that my current job gears me towards writing online tutorials. Do I still need to write more or less the same things again? It also makes me rethink: What was the original intention for writing an automated content analysis textbook? Or put it more blatantly, what is the selfish goal for this?

Back in the day, the selfish goal for this was to claim as methodological expert. But I think the problem with this is that I am more like self-proclaiming as methodological expert instead. Do the field consider me as a methodological expert? I think even if the textbook were finished, the answer is probably no. No one would think about me when it comes to automated content analysis. But the philosophical question actually is: Even if I am a methodological expert, what’s the good of it? Becoming a professor? Having more (grant) money and fame? I can’t tell anymore. Maybe I have been trapped in the spiral of academic nihilism.

Besides, several books have already been written on this topic since 2019. Text as Data by Grimmer, Roberts, & Stewart; Computational Analysis of Communication by van Atteveldt, Trilling, & Arcila; Introduction to Automated Content Analysis in R (and the German version Text as Data - Automatisierte Inhaltsanalyse in der Kommunikationswissenschaft) by Hase; Automatisierte Inhaltsanalyse mit R by Puschmann (and the English translation by Puschmann & Haim). Even the creators of Quanteda are writing a textbook called Text Analysis Using R. Many of these textbooks are open. These author are the real experts one should look up to, because a bare idea (like mine) is cheap but implementations (like theirs) are queen. Does the field need yet another (unfinished) textbook by me? I don’t think so. Also, would an unfinished textbook make any impact? Probably not.

I think my productivity could be used in some aspects that (probably only) I care about but with potentially tremendous impact. And surely that’s not the now-failed automated content analysis text book project.

And here is the “Projekt”. But let’s talk about the invisible work first.

Invisibility of my work

I have watched this video about invisible work and the idea of “brag document”. OK! I must say that those are some of the most North American ideas about work I’ve ever seen. But sure enough, I think I have done quite some “invisible work”, in their language. BTW, invisible work is the work that won’t get recognized. People —bosses— won’t even notice. Some workers, for example the people I linked to above, thought that one should not do invisible work. But the farm boy mentality inside me told me one shouldn’t brag. Like my parents and grandparents who do the “invisible” work of providing safe, tasty, and nutritious food.

Of course, it isn’t fair that some people did very little but got a lot of recognition. But some other people, like my parents and grandparents, work thanklessly day-and-night but need/get no recognition. Probably those people are too shy to brag. Or even, they disdain people who did very little but brag a lot.

I have also watched a recent talk by Yukihiro Matsumoto about the real world impact of Ruby. It is extremely easy to say Ruby is dead or something like that without considering the fact that 47 public companies primarily using Ruby (Airbnb, Shopify, and German companies such as Xing) have a total market cap of USD 212 billion. Github, Gitlab, and Heroku are mostly written in Ruby, which many companies rely on. Ruby is creating value (monetary or not, look at all the academic pages created with Github Pages… all Ruby; the hottest social media right now, Mastodon…) behind the scene. In my opinion, just the people using/building Ruby are not those cool kids who always need to say cute and sweet things on social media to attract attention, or to brag. Or, maybe some people who use brew (written in Ruby) to install their favorite programming languages and then rant on Reddit saying Ruby is dead, I don’t know.

Too big of a detour. As said, I have done quite some “invisible work”. I would say those “invisible work” creates significantly more real-world impact than my “visible work”. One of my invisible work projects is readODS. And this project occupies 7 characters in my CV (of ~20000 characters), but it gets 1000 downloads a month.

readODS, issue 71, and ODS

I adopted readODS from Gerrit-Jan Schutten and readODS is now an rOpenSci project. But I would say that readODS is my troubled child.

On the one hand, it is the most-used R package under my maintainership. On the other hand, ODS is a horrible file format to work with. I am particularly hit by the issue 71 of readODS. The gist of the issue is that the UK government released a 70MB ODS file called “jts0501.ods” and there is no way to read this file using readODS due to RAM issues. It is actually related to several issues that users of readODS always complain: readODS is either slow, buggy, or unusable for reading and writing real-world ODS files.

The following are my excuses.

First, ODS is an XML-based format and XML is a “high noise, low signal” format. Let’s compare the dataset mtcars (first 10 rows) presented in csv (1.8K) and ODS-based XML (22.1K) at the end of this document. Also as a markup language in the same family, even exporting those ten rows as HTML takes 1/10 of that storage. Due to the noisiness, the format utilizes compression (basically zip) to compress the large XML file. The result ODS file (basically a zipped-up XML file plus some other bells and whistles) is still 3.4K. So, the above-mentioned 70MB “jts0501.ods” is actually 1.3GB unzipped.

For the purpose of data exchange, there is no reason to use ODS (I am saying this as the maintainer of readODS). The extra metadata hidden in the maze of XML structure (e.g. font, color, data type, cached result of spreadsheet formula) is useless for the rectangular data analytic tasks that one would do with R. A dataset generated by R from a data frame also doesn’t need to provide those information.

CSV is a horrible format too, but it is less horrible than ODS. In many systems, it is possible to read a large text file one line at a time. As a line in a CSV represents a row, it is possible to read data rowwise. A line of XML means nothing. To extract the structure of the data (which is in most of the time rectangular anyway), one must parse the entire XML into a DOM tree and then start from there. Only this difference makes CSV a better format for exchange of rectangular data. So, if you have data to share, just don’t use ODS.

Second, this high noise makes it super computational demanding to parse. It takes 1.3GB just to represent the data, imagine how many ram it takes to parse it. And thus the unusable performance of readODS. Also, one design choice (IMO, design fault) of ODS makes the parsing of it extremely slow. ODS has a technique to “save storage space” by representing repeated cells by row or by column. For one using a spreadsheet might have done it: type a value in a cell and then populate that cell to multiple other cells by that little square at the corner. That’s the root of all evils! That “storage saving technique” makes the parsing always needs to keep track of the current coordinate of a cell represented by the XML tag <table:table-cell>. You can’t tell the location of any given <table:table-cell> without considering all tags! Besides, in such a high-noise format, what’s the point of saving storage space of some repeated cells anyway?

Besides, all the other crazy maniacs such as merged cells, another “storage saving” technique to representing repeated spaces (that’s right, a string of “ “ is also represented in XML as <text:s text:c="2"/>). These design choices won’t create any value for data analytic or data sharing purposes. But create many, many, many, many, many, many, many, many, many, many possibilities for 1) buggy parsing; 2) checking for exceptions that wastes even more computational power.

OK. OK. You might at this point wanna argue with me that XLSX is also a zipped-up, XML-based format. It is just my poor programming skill that creates all these issues. As one individual commented about the fact that I introduced a bug into the code base of readODS and left there for 6 years. I quote: “[t]he package author had mistakenly interpretted” (emphasis added) something and thus the bug in readODS.

Mea culpa. I am not a decent programmer and I mistakenly interpretted things. But has anyone ever asked: Why the singular “the package author”, i.e. I, in the above quote? Shouldn’t it be a collective noun such as a company, a community, a government agency, a group? Why the breakage of a package depends on just one person, the singular “package author”? And that actually brings us to the third reason.

Microsoft’s XLSX (a.k.a. ooxml) is also a horrible format due to the same reason of high noise XML above. However, due to Microsoft Excel’s market dominance, it makes a lot of business sense to create highly-optimized I/O facilities for the also-very-noisy XLSX format. Therefore, XLSX parsing attracts some of the best talents in the R community to work on it: readxl (RStudio’s Hadley Wickham, Jenny Brian and many contributors), writexl (Jeroen Ooms et al.), and tidyxl (Duncan Garmonsway et al.). There is no business reason to support ODS. ODS, as a format, is only worthy because it is a good clause to support an open standard (CSV is also an open standard, BTW). As an analogy, it is like supporting Ogg Vorbis. Never heard of it? The case in point 1.

Due to this very reason, software support for ODS is usually not done by large organizations such as RStudio, but some random developers. Even the C++ library orcus used in LibreOffice is largely maintained by just one highly motivated individual. To be honest, I am not as motivated as him. LibreOffice can read that “jts0501.ods” file in around 10s. readODS, as stated in the issue 71, taps out. As readODS is mostly a single-person project, the package author (singular) should be blamed for its failure 2.

Alternatives

Not using ODS

Actually, issue 71 can be easily “solved” by not using ODS. As I said, LibreOffice can read that “jts0501.ods” file. One can just open that ODS file and save the sheet you want to use as CSV (which is 9MB). And readr can read 32,845 rows and 86 columns in less than 1 second.

require(readr)
system.time(x <- read_csv("jts0501.csv", skip = 5))

Even converting the entire file to XLSX works (readxl can read 32,845 rows and 86 columns in less than 2 second).

require(readxl)
system.time(read_xlsx("jts0501.xlsx", skip = 5, sheet = 2))

Including the file conversion, the whole thing can be done in less than 1 minute. But that’s not a solution one usually imagines.

Python

All the cool kids in the data analytic space are writing Python now. The ODS support for Python is actually in a less sorry state. The European Environmental Agency (EEA, one of the many EU agencies) is supporting odfpy. Pandas has odf support out of the box (if odfpy is available).

But I can’t use pandas to read “jts0501.ods” and it taps out precisely with the same reason (out of memory) after 5 minutes. But it can read the converted XLSX file, though.

import pandas as pd
x = pd.read_excel("jts0501.ods", sheet_name = 1, skiprows = 4)

Alternative implementations such as ezodf taps out immediately. It is worth mentioning that Julia’s ods capability is based on ezodf.

In summary, switching to Python won’t help at this moment.

Javascript

“Any application that can be written in JavaScript, will eventually be written in JavaScript” - Atwood’s Law

The JS engine V8 is insanely fast. But can it read “jts0501.ods”? The omni spreadsheet reading library SheetJS can read ODS. Let’s give it a go with NodeJS.

XLSX = require('xlsx');
var workbook = XLSX.readFile('jts0501.ods');

It dies. In fact, it can’t even read the exported XLXS file that readxl can read.

Introducing Projekt 71

The point of bringing up Python and JS is not to invoking Tu quoque argument. But to say that it is almost impossible to parse “jts0501.ods” with a parser written purely in an interpreted language such as R. The reason why Libreoffice (and its underlying orcus library) can read ODS and readxl can read XLSX is the same: they are not written in an interpreted language. orcus, as said, is a C++ library. Similarly, readxl is an R package but the heavy-lifting parts are written in C++, based on RapidXML.

For a long time, I know the only solution to the performance issues of readODS is to embark on yet another rewrite to make it more like readxl. In case you don’t know, readODS has been rewritten almost from the ground up once in 2016 to change from using XML to xml2. This kind of constant rewriting is actually an antipattern. But I hope that this is the last time I’ll need to rewrite readODS. Before stating my planned project, it is worth mentioned that the individual who commented about my individual mistake to leave a bug in the codebase is a contributor of readODS and is also embarking on creating another way to read ODS file in R using xml2. The project is called tidyods. I endorse this effort and I am wholeheartedly thankful that he takes this effort. Please have more people put effort in this space.

So, here is my part: I’ll devote my 2023 to the project I tentatively called “Projekt 71”. The idea is simple: I want to have a way that can read the aforementioned “jts0501.ods” directly as an R data frame without memory issues; but yet pass at least 80% of the current unit tests of readODS. So, I am embarking on solving just one Github issue of readODS. I will put other of my R packages into maintenance mode and focus only on this.

To be honest, if one gives this task to a skilled programmer to work on it full time, it can be solved in a few hours. But I need a year. I have some experience in C++ and Rcpp, but I am not competent enough to incorporate orcus into readODS. Actually, I have another idea in mind. But I will let you know when I have some progress.

This project might fail, similar to my failed book project. But time will tell.

Readjusting my 2023 objectives

I will keep just these 3: “Think out loud”, “Contribute to open source”, “Publish top papers”. And I will subsume Projekt 71 under “Contribute to open source”.

Appendix

10 rows of mtcars in csv.

"","mpg","cyl","disp","hp","drat","wt","qsec","vs","am","gear","carb"
"Mazda RX4",21,6,160,110,3.9,2.62,16.46,0,1,4,4
"Mazda RX4 Wag",21,6,160,110,3.9,2.875,17.02,0,1,4,4
"Datsun 710",22.8,4,108,93,3.85,2.32,18.61,1,1,4,1
"Hornet 4 Drive",21.4,6,258,110,3.08,3.215,19.44,1,0,3,1
"Hornet Sportabout",18.7,8,360,175,3.15,3.44,17.02,0,0,3,2
"Valiant",18.1,6,225,105,2.76,3.46,20.22,1,0,3,1
"Duster 360",14.3,8,360,245,3.21,3.57,15.84,0,0,3,4
"Merc 240D",24.4,4,146.7,62,3.69,3.19,20,1,0,4,2
"Merc 230",22.8,4,140.8,95,3.92,3.15,22.9,1,0,4,2
"Merc 280",19.2,6,167.6,123,3.92,3.44,18.3,1,0,4,4

10 rows of mtcars in ODS xml.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<office:document-content xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" office:version="1.2">
  <office:font-face-decls>
    <style:font-face style:name="Calibri" svg:font-family="Calibri"/>
  </office:font-face-decls>
  <office:automatic-styles>
    <style:style style:name="ce1" style:family="table-cell" style:parent-style-name="Default" style:data-style-name="N0"/>
    <style:style style:name="co1" style:family="table-column">
      <style:table-column-properties fo:break-before="auto" style:column-width="1.69333333333333cm"/>
    </style:style>
    <style:style style:name="ro1" style:family="table-row">
      <style:table-row-properties style:row-height="15pt" style:use-optimal-row-height="true" fo:break-before="auto"/>
    </style:style>
    <style:style style:name="ta1" style:family="table" style:master-page-name="mp1">
      <style:table-properties table:display="true" style:writing-mode="lr-tb"/>
    </style:style>
  </office:automatic-styles>
  <office:body>
    <office:spreadsheet>
      <table:calculation-settings table:case-sensitive="false" table:search-criteria-must-apply-to-whole-cell="true" table:use-wildcards="true" table:use-regular-expressions="false" table:automatic-find-labels="false"/>
      <table:table table:name="Sheet1" table:style-name="ta1">
        <table:table-column table:style-name="co1" table:number-columns-repeated="16384" table:default-cell-style-name="ce1"/>
        <table:table-row>
          <table:table-cell office:value-type="string" office:value="mpg" table:style-name="ce1">
            <text:p>mpg</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="cyl" table:style-name="ce1">
            <text:p>cyl</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="disp" table:style-name="ce1">
            <text:p>disp</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="hp" table:style-name="ce1">
            <text:p>hp</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="drat" table:style-name="ce1">
            <text:p>drat</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="wt" table:style-name="ce1">
            <text:p>wt</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="qsec" table:style-name="ce1">
            <text:p>qsec</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="vs" table:style-name="ce1">
            <text:p>vs</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="am" table:style-name="ce1">
            <text:p>am</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="gear" table:style-name="ce1">
            <text:p>gear</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="string" office:value="carb" table:style-name="ce1">
            <text:p>carb</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="21" table:style-name="ce1">
            <text:p>21</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="6" table:style-name="ce1">
            <text:p>6</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="160" table:style-name="ce1">
            <text:p>160</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="110" table:style-name="ce1">
            <text:p>110</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.9" table:style-name="ce1">
            <text:p>3.9</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2.62" table:style-name="ce1">
            <text:p>2.62</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="16.46" table:style-name="ce1">
            <text:p>16.46</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="21" table:style-name="ce1">
            <text:p>21</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="6" table:style-name="ce1">
            <text:p>6</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="160" table:style-name="ce1">
            <text:p>160</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="110" table:style-name="ce1">
            <text:p>110</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.9" table:style-name="ce1">
            <text:p>3.9</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2.875" table:style-name="ce1">
            <text:p>2.875</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="17.02" table:style-name="ce1">
            <text:p>17.02</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="22.8" table:style-name="ce1">
            <text:p>22.8</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="108" table:style-name="ce1">
            <text:p>108</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="93" table:style-name="ce1">
            <text:p>93</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.85" table:style-name="ce1">
            <text:p>3.85</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2.32" table:style-name="ce1">
            <text:p>2.32</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="18.61" table:style-name="ce1">
            <text:p>18.61</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="21.4" table:style-name="ce1">
            <text:p>21.4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="6" table:style-name="ce1">
            <text:p>6</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="258" table:style-name="ce1">
            <text:p>258</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="110" table:style-name="ce1">
            <text:p>110</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.08" table:style-name="ce1">
            <text:p>3.08</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.215" table:style-name="ce1">
            <text:p>3.215</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="19.44" table:style-name="ce1">
            <text:p>19.44</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3" table:style-name="ce1">
            <text:p>3</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="18.7" table:style-name="ce1">
            <text:p>18.7</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="8" table:style-name="ce1">
            <text:p>8</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="360" table:style-name="ce1">
            <text:p>360</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="175" table:style-name="ce1">
            <text:p>175</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.15" table:style-name="ce1">
            <text:p>3.15</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.44" table:style-name="ce1">
            <text:p>3.44</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="17.02" table:style-name="ce1">
            <text:p>17.02</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3" table:style-name="ce1">
            <text:p>3</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2" table:style-name="ce1">
            <text:p>2</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="18.1" table:style-name="ce1">
            <text:p>18.1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="6" table:style-name="ce1">
            <text:p>6</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="225" table:style-name="ce1">
            <text:p>225</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="105" table:style-name="ce1">
            <text:p>105</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2.76" table:style-name="ce1">
            <text:p>2.76</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.46" table:style-name="ce1">
            <text:p>3.46</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="20.22" table:style-name="ce1">
            <text:p>20.22</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3" table:style-name="ce1">
            <text:p>3</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="14.3" table:style-name="ce1">
            <text:p>14.3</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="8" table:style-name="ce1">
            <text:p>8</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="360" table:style-name="ce1">
            <text:p>360</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="245" table:style-name="ce1">
            <text:p>245</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.21" table:style-name="ce1">
            <text:p>3.21</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.57" table:style-name="ce1">
            <text:p>3.57</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="15.84" table:style-name="ce1">
            <text:p>15.84</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3" table:style-name="ce1">
            <text:p>3</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="24.4" table:style-name="ce1">
            <text:p>24.4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="146.7" table:style-name="ce1">
            <text:p>146.7</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="62" table:style-name="ce1">
            <text:p>62</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.69" table:style-name="ce1">
            <text:p>3.69</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.19" table:style-name="ce1">
            <text:p>3.19</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="20" table:style-name="ce1">
            <text:p>20</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2" table:style-name="ce1">
            <text:p>2</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="22.8" table:style-name="ce1">
            <text:p>22.8</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="140.8" table:style-name="ce1">
            <text:p>140.8</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="95" table:style-name="ce1">
            <text:p>95</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.92" table:style-name="ce1">
            <text:p>3.92</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.15" table:style-name="ce1">
            <text:p>3.15</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="22.9" table:style-name="ce1">
            <text:p>22.9</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="2" table:style-name="ce1">
            <text:p>2</text:p>
          </table:table-cell>
        </table:table-row>
        <table:table-row>
          <table:table-cell office:value-type="float" office:value="19.2" table:style-name="ce1">
            <text:p>19.2</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="6" table:style-name="ce1">
            <text:p>6</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="167.6" table:style-name="ce1">
            <text:p>167.6</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="123" table:style-name="ce1">
            <text:p>123</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.92" table:style-name="ce1">
            <text:p>3.92</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="3.44" table:style-name="ce1">
            <text:p>3.44</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="18.3" table:style-name="ce1">
            <text:p>18.3</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="1" table:style-name="ce1">
            <text:p>1</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="0" table:style-name="ce1">
            <text:p>0</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
          <table:table-cell office:value-type="float" office:value="4" table:style-name="ce1">
            <text:p>4</text:p>
          </table:table-cell>
        </table:table-row>
      </table:table>
    </office:spreadsheet>
  </office:body>
</office:document-content>

  1. I just wanted to test the name recognition of the format. It was advertised as a kind of open source MP3 killer. Actually it does. One probably didn’t search for Ogg files on Napster or doesn’t produce any Ogg file. But many people actually have listened to Ogg files: the Spotify desktop / mobile apps are actually streaming Ogg. 

  2. Previously, I wrote that when the system collapses and your software helps, the authors will be acknowledged as a group (e.g. “R package developers”). Now I know that if the system collapses because of your software, the singular developer is to be blamed. Würden wir Helden für einen Tag. Aber für immer bin ich nutzlos. 


Powered by Jekyll and profdr theme