Home > Blockchain >  Convert huge csv to hdf5 format
Convert huge csv to hdf5 format

Time:10-02

I downloaded IBM's Airline Reporting Carrier On-Time Performance Dataset; the uncompressed CSV is 84 GB. I want to run an analysis, similar to Flying high with Vaex, with the vaex libary.

I tried to convert the CSV to a hdf5 file, to make it readable for the vaex libary:

import time
import vaex
start=time.time()
df = vaex.from_csv(r"D:\airline.csv", convert=True, chunk_size=1000000)
end=time.time() 
print("Time:",(end-start),"Seconds")

I always get an error when running the code:

RuntimeError: Dirty entry flush destroy failed (file write failed: time = Fri Sep 30 17:58:55 2022
, filename = 'D:\airline.csv_chunk_8.hdf5', file descriptor = 7, errno = 22, error message = 'Invalid argument', buf = 0000021EA8C6B128, total write size = 2040, bytes this sub-write = 2040, bytes actually written = 18446744073709551615, offset = 221133661).

Second run, I get this error:

RuntimeError: Unable to flush file's cached information (file write failed: time = Fri Sep 30 20:18:19 2022
, filename = 'D:\airline.csv_chunk_18.hdf5', file descriptor = 7, errno = 22, error message = 'Invalid argument', buf = 000002504659B828, total write size = 2048, bytes this sub-write = 2048, bytes actually written = 18446744073709551615, offset = 348515307)

Is there an alternative way to convert the CSV to hdf5 without Python? For example, a downloadable software which can do this job?

CodePudding user response:

I'm not familiar with vaex, so can't help with usage and functions. However, I can read error messages. :-)

It reports "bytes written" with a huge number (18_446_744_073_709_551_615), much larger than the 84GB CSV. Some possible explanations:

  1. you ran out of disk
  2. you ran out of memory, or
  3. had some other error

To diagnose, try testing with a small csv file and see if vaex.from_csv() works as expected. I suggest the lax_to_jfk.csv file.

Regarding your question, is there an alternative way to convert a csv to hdf5?, why not use Python?

Are you more comfortable with other languages? If so, you can install HDF5 and write your code with their C or Fortran API.

OTOH, if you are familiar with Python, there are other packages you can use to read the CSV file and create the HDF5 file.

Python packages to read the CSV
Personally, I like NumPy's genfromtxt() to read the CSV (You can also use loadtxt() to read the CSV, if you don't have missing values and don't need the field names.) However, I think you will run into memory problems reading a 84GB file. That said, you can use the skip_header and max_rows parameters with genfromtxt() to read and load a subset of lines. Alternately you can use csv.DictReader(). It reads a line at a time. So, you avoid memory issues, but it could be very slow loading the HDF5 file.

Python packages to create the HDF5 file
I have used both h5py and pytables (aka tables) to create and read HDF5 files. Once you load the CSV data to a NumPy array, it's a snap to create the HDF5 dataset.

Here is a very simple example that reads the lax_to_jfk.csv data and loads to a HDF5 file.

csv_name = 'lax_to_jfk'
rec_arr = np.genfromtxt(csv_name '.csv', delimiter=',',
                        dtype=None, names=True, encoding='bytes')

with h5py.File(csv_name '.h5', 'w') as h5f:
    h5f.create_dataset(csv_name,data=rec_arr)

Update:
After posting this example, I decided to test with a larger file (airline_2m.csv). It's 861 MB, and has 2M rows. I discovered the code above doesn't work. However, it's not because of the number of rows. The problem is the columns (field names). Turns out the data isn't as clean; there are 109 field names on row 1, and some rows have 111 columns of data. As a result, the auto-generated dtype doesn't have a matching field. While investigating this, I also discovered many rows only have the values for first 56 fields. In other words, fields 57-111 are not very useful. One solution to this is to add the usecols=() parameter. Code below reflects this modification, and works with this test file. (I have not tried testing with your large file airline.csv. Given it's size likely you will need to read and load incrementally.)

csv_name = 'airline_2m'
rec_arr = np.genfromtxt(csv_name '.csv', delimiter=',',
                        dtype=None, names=True, encoding='bytes') #,
                        usecols=(i for i in range(56)) )

with h5py.File(csv_name '.h5', 'w') as h5f:
    h5f.create_dataset(csv_name,data=rec_arr)

CodePudding user response:

I tried reproducing your example. I believe the problem you are facing is quite common when dealing with CSVs. The schema is not known.

Sometimes there are "mixed types" and pandas (used underneath vaex's read_csv or from_csv ) casts those columns as dtype object.

Vaex does not really support such mixed dtypes, and requires each column to be of a single uniform type (kind of a like a database).

So how to go around this? Well, the best way I can think of is to use the dtype argument to explicitly specify the types of all columns (or those that you suspect or know to have mixed types). I know this file has like 100 columns and that's annoying.. but that is also kind of the price to pay when using a format such as CSV...

Another thing i noticed is the encoding.. using pure pandas.read_csv failed at some point because of encoding and requires one to add encoding="ISO-8859-1". This is also supported by vaex.open (since the args are just passed down to pandas).

In fact if you want to do manually what vaex.open does automatically for you (given that this CSV file might not be as clean as one would hope), do something like (this is pseudo code but I hope close to the real thing)

# Iterate over the file in chunks
for i, df_tmp in enumerate(pd.read_csv(file, chunksize=11_000_000, encoding="ISO-8859-1", dtype=dtype)):
    # Assert or check or do whatever needs doing to ensure column types are as they should be
    
    # Pass the data to vaex (this does not take extra RAM):
    df_vaex = vaex.from_pandas(df_tmp)
    # Export this chunk into HDF5
    # df_vaex.export_hdf5(f'chunk_{i}.hdf5')

# When the above loop finishes, just concat and export the data to a single file if needed (gives some performance benefit).
df = vaex.open('chunk*.hdf5')
df.export_hdf5('converted.hdf5', progress='rich')

I've seen potentially much better/faster way of doing this with vaex, but it is not released yet (i saw it in the code repo on github), so I will not go into it, but if you can install from source, and want me to elaborate further feel free to drop a comment.

Hope this at least gives some ideas on how to move forward.

  • Related