78 karma · joined August 12, 2015
I might disagree with some on your list, such as the below: - Excel can losslessly save CSVs it opens
This is not always false, depending on how careful the user is. Double-clicking to open a CSV file is definitely, but using the menu to import data, then going through the UI to treat every column as Text (and not General) should be fine
I might also add a couple: - A header row, if present, only spans a single row
1. use "un-integrated" python code e.g. run a subprocess independently that just runs an external binary / shared lib / etc
2. avoid solutions that do not require a python environment entirely, or, use them in a less-commonly-employed manner that would not run the product through python (e.g. Airflow bash operator)
Both of the above seem perfectly reasonable to me, but I'm not sure whether others would agree.
With platforms like DataBricks and Airflow that are heavily inclined towards python, it feels somewhat unavoidable to offer a python "import myproduct" option. But, is it truly necessary to be part of that python ecosystem to survive as a data product?
I know: python has a vibrant community, it's a virtuous cycle because every data lib is in python and therefore every data platform / analyst uses it etc, why would you ever not want to just add your product to that ecosystem etc. I get that, but that's also why I'm asking the question
The intended context of the question, which admittedly could be more clear-- is for a data product that could be used for any data. Take Cockroach DB for example, which already has python integration ( https://www.cockroachlabs.com/blog/sample-app-python-and-coc... )
With platforms like DataBricks and Airflow that are heavily inclined towards python, it feels somewhat unavoidable to need to offer a way to access the product via "import" in python.
So the question is: in that scenario, could a commercial product still survive-- or even thrive-- without doing so?
Running "select *" on a 1mm-row worldcitiespop_mil file, q takes 27 seconds compared to `zsv sql` which takes 1.7 seconds ( https://github.com/liquidaty/zsv ) and also supports multiple file joins. I'm sure q is faster once cached, but taking a 16x performance hit up-front is not for me
But, the change in rates over the last 12 months -- a 97% increase from 2.93-- that is something new that the US market has never experienced before
An example of output that fits this paradigm you describe-- but to a much further degree-- would be the dozens of tables shown in the securities offering described at https://www.sec.gov/Archives/edgar/data/0001561167/000114420... search for "Stated Principal Balances of the Mortgage Loans as of the Cut-off Date" (on page A-3).
How do you generate reports like this in a manner that is flexible for end users without requiring IT in the middle? You start with the bare input, which is: data + report logic:
1. Specify the common columns that you want in your output tables (i.e. columns other than the first). In your example, that would be order_count, gmv, net_gmv
2. Separately, specify the tables that you want to generate, where each table spec consists of:
- the bucket logic (in your example, that would be a formula representing "age broken out in buckets of 10")
- optional other characteristics such as whether the data should be filtered before going into the table, which column the table should be sorted on, whether to limit the table output to the "top [10]" rows, etc etc
3. Third, run your data, plus the above spec, through some software that will generate your report for youAs for part 3, my company has recently launched a free platform for doing all of the above in a collaborative and secure manner. Please reach out if you'd like more info on this. Of course, you can do it yourself or have your IT do it-- but be aware it is not as easy as it sounds when you start having to deal with real-world practicalities like schema variability and scalability. And anyway, why bother if you can now do it all for free?
fastest to slowest: zsv (0.07), xsv (0.16), goawk (0.42), python (~1.6)
Obviously, does not tell the whole story as this test was limited to "count" and an interpreted language is expected to always be slower compared to a precompiled command, but, it might be relevant to a user deciding what tool to use. Also, might be instructive as to room for improvement in the go code (or possibly the go code could use the c lib)-- I note that even if the goawk command is '{}' the runtime is still about the same.
full results:
---
goawk:
1000001 7000007 real 0m0.435s user 0m0.435s sys 0m0.031s
1000001 7000007 real 0m0.413s user 0m0.419s sys 0m0.024s
1000001 7000007 real 0m0.425s user 0m0.430s sys 0m0.024s
xsv:
1000000 real 0m0.157s user 0m0.141s sys 0m0.013s
1000000 real 0m0.156s user 0m0.141s sys 0m0.012s
1000000 real 0m0.158s user 0m0.142s sys 0m0.013s
zsv:
1000000 real 0m0.066s user 0m0.053s sys 0m0.010s
1000000 real 0m0.077s user 0m0.060s sys 0m0.012s
1000000 real 0m0.069s user 0m0.056s sys 0m0.010s
python:
1000001 7000007 real 0m1.589s user 0m1.553s sys 0m0.026s
1000001 7000007 real 0m1.583s user 0m1.550s sys 0m0.025s
1000001 7000007 real 0m2.122s user 0m1.675s sys 0m0.037s
---
The script for this was:
---
echo 'goawk:'
(time goawk -i csv '{ w+=NF } END { print NR, w }' < worldcitiespop_mil.csv) 2>&1 | xargs
(time goawk -i csv '{ w+=NF } END { print NR, w }' < worldcitiespop_mil.csv) 2>&1 | xargs
(time goawk -i csv '{ w+=NF } END { print NR, w }' < worldcitiespop_mil.csv) 2>&1 | xargs
echo 'xsv:'
(time xsv count < worldcitiespop_mil.csv) 2>&1 | xargs
(time xsv count < worldcitiespop_mil.csv) 2>&1 | xargs
(time xsv count < worldcitiespop_mil.csv) 2>&1 | xargs
echo 'zsv:'
(time zsv count < worldcitiespop_mil.csv) 2>&1 | xargs
(time zsv count < worldcitiespop_mil.csv) 2>&1 | xargs
(time zsv count < worldcitiespop_mil.csv) 2>&1 | xargs
echo 'python:'
(time python3 count.py < worldcitiespop_mil.csv) 2>&1 | xargs
(time python3 count.py < worldcitiespop_mil.csv) 2>&1 | xargs
(time python3 count.py < worldcitiespop_mil.csv) 2>&1 | xargs
1. Split into multiple shard using e.g. `split`, where each piece is small enough to fit comfortably into memory 2. Sort each shard. You could use something like `zsv` (sql sub-command) (https://github.com/liquidaty/zsv) or `xsv` (sort subcommand) (https://github.com/BurntSushi/xsv) for this 3. Use mergesort to sort all shards, at the same time, into a single output table, and in the process, de-dupe and/or merge (if you had too many shards for your operating system to allow so many files to be open at once, you might need to do an intermediate merge to consolidate shards)
For #3, I don't know of a tool to do this-- probably one exists for simple de-duping, but it may be harder if you need support for merge logic. If a tool for this does not exist, I would be imagine `zsv` could quite be easily extended to handle it.
Instead of just modifying your parser, you change your entire input format to be incompatible with what most of the rest of the world uses (standard JSON).
I get it, it requires slightly more technical skill / effort / etc to implement a proper streaming JSON parser (which BTW will perform WAY better in terms of speed and memory) instead of just writing "my_data.split('\n')". But, there are pre-existing libraries that already do that, and a one-time tiny bit of extra work, to open up a capability that you can reuse anywhere in your stack, seems to be a much better option than a lifetime of incompatibility with standard JSON.
Couple further comments: - I tried copy + paste in your 1mm cells demo, and it didn't behave as expected (copied "n;m", pasted "n") - 1mm cells isn't very much if you are touting performance. 100k rows with 500 columns each = 50,000,000 cells; that would be a better example if you are trying to show that your lib handles large tables well. And if you are trying to suggest that this could be compared to Excel, then 1mm rows with 1000 columns would be better (Excel supports 1,048,576 rows by 16,384 columns)
anyway, looks like a nice start-- good luck!