1178

I have tried to puzzle out an answer to this question for many months while learning pandas. I use SAS for my day-to-day work and it is great for it's out-of-core support. However, SAS is horrible as a piece of software for numerous other reasons.

One day I hope to replace my use of SAS with python and pandas, but I currently lack an out-of-core workflow for large datasets. I'm not talking about "big data" that requires a distributed network, but rather files too large to fit in memory but small enough to fit on a hard-drive.

My first thought is to use HDFStore to hold large datasets on disk and pull only the pieces I need into dataframes for analysis. Others have mentioned MongoDB as an easier to use alternative. My question is this:

What are some best-practice workflows for accomplishing the following:

  1. Loading flat files into a permanent, on-disk database structure
  2. Querying that database to retrieve data to feed into a pandas data structure
  3. Updating the database after manipulating pieces in pandas

Real-world examples would be much appreciated, especially from anyone who uses pandas on "large data".

Edit -- an example of how I would like this to work:

  1. Iteratively import a large flat-file and store it in a permanent, on-disk database structure. These files are typically too large to fit in memory.
  2. In order to use Pandas, I would like to read subsets of this data (usually just a few columns at a time) that can fit in memory.
  3. I would create new columns by performing various operations on the selected columns.
  4. I would then have to append these new columns into the database structure.

I am trying to find a best-practice way of performing these steps. Reading links about pandas and pytables it seems that appending a new column could be a problem.

Edit -- Responding to Jeff's questions specifically:

  1. I am building consumer credit risk models. The kinds of data include phone, SSN and address characteristics; property values; derogatory information like criminal records, bankruptcies, etc... The datasets I use every day have nearly 1,000 to 2,000 fields on average of mixed data types: continuous, nominal and ordinal variables of both numeric and character data. I rarely append rows, but I do perform many operations that create new columns.
  2. Typical operations involve combining several columns using conditional logic into a new, compound column. For example, if var1 > 2 then newvar = 'A' elif var2 = 4 then newvar = 'B'. The result of these operations is a new column for every record in my dataset.
  3. Finally, I would like to append these new columns into the on-disk data structure. I would repeat step 2, exploring the data with crosstabs and descriptive statistics trying to find interesting, intuitive relationships to model.
  4. A typical project file is usually about 1GB. Files are organized into such a manner where a row consists of a record of consumer data. Each row has the same number of columns for every record. This will always be the case.
  5. It's pretty rare that I would subset by rows when creating a new column. However, it's pretty common for me to subset on rows when creating reports or generating descriptive statistics. For example, I might want to create a simple frequency for a specific line of business, say Retail credit cards. To do this, I would select only those records where the line of business = retail in addition to whichever columns I want to report on. When creating new columns, however, I would pull all rows of data and only the columns I need for the operations.
  6. The modeling process requires that I analyze every column, look for interesting relationships with some outcome variable, and create new compound columns that describe those relationships. The columns that I explore are usually done in small sets. For example, I will focus on a set of say 20 columns just dealing with property values and observe how they relate to defaulting on a loan. Once those are explored and new columns are created, I then move on to another group of columns, say college education, and repeat the process. What I'm doing is creating candidate variables that explain the relationship between my data and some outcome. At the very end of this process, I apply some learning techniques that create an equation out of those compound columns.

It is rare that I would ever add rows to the dataset. I will nearly always be creating new columns (variables or features in statistics/machine learning parlance).

Asclepius
  • 57,944
  • 17
  • 167
  • 143
Zelazny7
  • 39,946
  • 18
  • 70
  • 84
  • 1
    Is the ratio core size / full size 1 %, 10 % ? Does it matter -- if you could compress cols to int8, or filter out noisy rows, would that change your compute-think loop from say hours to minutes ? (Also add tag large-data.) – denis Mar 18 '13 at 11:26
  • 1
    Storing float32 instead of float64, and int8 where possible, *should* be trivial (don't know what tools / functions do float64 internally though) – denis Mar 18 '13 at 11:59
  • can you split your task into chunks of work? – Andrew Scott Evans Dec 08 '17 at 03:09
  • 1
    a nice 2019 solution to doing pandas like operations on "medium" data that don't fit in memory is [dask](https://examples.dask.org/) – lunguini Oct 30 '19 at 17:56
  • There are alternatives to python+pandas which you might want to consider seeing as you're just starting out. Consider the fact that Python is a general-purpose programming language (not a DSL for data munging and analysis) and that pandas is a library tacked on top of that. I would consider looking at R or kdb. – Henry Henrinson Nov 16 '19 at 14:02
  • 1
    duckdb is shaping up to be a good alternative to working on medium sized datasets on a single machine. – Zelazny7 Dec 15 '20 at 17:28

16 Answers16

716

I routinely use tens of gigabytes of data in just this fashion e.g. I have tables on disk that I read via queries, create data and append back.

It's worth reading the docs and late in this thread for several suggestions for how to store your data.

Details which will affect how you store your data, like:
Give as much detail as you can; and I can help you develop a structure.

  1. Size of data, # of rows, columns, types of columns; are you appending rows, or just columns?
  2. What will typical operations look like. E.g. do a query on columns to select a bunch of rows and specific columns, then do an operation (in-memory), create new columns, save these.
    (Giving a toy example could enable us to offer more specific recommendations.)
  3. After that processing, then what do you do? Is step 2 ad hoc, or repeatable?
  4. Input flat files: how many, rough total size in Gb. How are these organized e.g. by records? Does each one contains different fields, or do they have some records per file with all of the fields in each file?
  5. Do you ever select subsets of rows (records) based on criteria (e.g. select the rows with field A > 5)? and then do something, or do you just select fields A, B, C with all of the records (and then do something)?
  6. Do you 'work on' all of your columns (in groups), or are there a good proportion that you may only use for reports (e.g. you want to keep the data around, but don't need to pull in that column explicity until final results time)?

Solution

Ensure you have pandas at least 0.10.1 installed.

Read iterating files chunk-by-chunk and multiple table queries.

Since pytables is optimized to operate on row-wise (which is what you query on), we will create a table for each group of fields. This way it's easy to select a small group of fields (which will work with a big table, but it's more efficient to do it this way... I think I may be able to fix this limitation in the future... this is more intuitive anyhow):
(The following is pseudocode.)

import numpy as np
import pandas as pd

# create a store
store = pd.HDFStore('mystore.h5')

# this is the key to your storage:
#    this maps your fields to a specific group, and defines 
#    what you want to have as data_columns.
#    you might want to create a nice class wrapping this
#    (as you will want to have this map and its inversion)  
group_map = dict(
    A = dict(fields = ['field_1','field_2',.....], dc = ['field_1',....,'field_5']),
    B = dict(fields = ['field_10',......        ], dc = ['field_10']),
    .....
    REPORTING_ONLY = dict(fields = ['field_1000','field_1001',...], dc = []),

)

group_map_inverted = dict()
for g, v in group_map.items():
    group_map_inverted.update(dict([ (f,g) for f in v['fields'] ]))

Reading in the files and creating the storage (essentially doing what append_to_multiple does):

for f in files:
   # read in the file, additional options may be necessary here
   # the chunksize is not strictly necessary, you may be able to slurp each 
   # file into memory in which case just eliminate this part of the loop 
   # (you can also change chunksize if necessary)
   for chunk in pd.read_table(f, chunksize=50000):
       # we are going to append to each table by group
       # we are not going to create indexes at this time
       # but we *ARE* going to create (some) data_columns

       # figure out the field groupings
       for g, v in group_map.items():
             # create the frame for this group
             frame = chunk.reindex(columns = v['fields'], copy = False)    

             # append it
             store.append(g, frame, index=False, data_columns = v['dc'])

Now you have all of the tables in the file (actually you could store them in separate files if you wish, you would prob have to add the filename to the group_map, but probably this isn't necessary).

This is how you get columns and create new ones:

frame = store.select(group_that_I_want)
# you can optionally specify:
# columns = a list of the columns IN THAT GROUP (if you wanted to
#     select only say 3 out of the 20 columns in this sub-table)
# and a where clause if you want a subset of the rows

# do calculations on this frame
new_frame = cool_function_on_frame(frame)

# to 'add columns', create a new group (you probably want to
# limit the columns in this new_group to be only NEW ones
# (e.g. so you don't overlap from the other tables)
# add this info to the group_map
store.append(new_group, new_frame.reindex(columns = new_columns_created, copy = False), data_columns = new_columns_created)

When you are ready for post_processing:

# This may be a bit tricky; and depends what you are actually doing.
# I may need to modify this function to be a bit more general:
report_data = store.select_as_multiple([groups_1,groups_2,.....], where =['field_1>0', 'field_1000=foo'], selector = group_1)

About data_columns, you don't actually need to define ANY data_columns; they allow you to sub-select rows based on the column. E.g. something like:

store.select(group, where = ['field_1000=foo', 'field_1001>0'])

They may be most interesting to you in the final report generation stage (essentially a data column is segregated from other columns, which might impact efficiency somewhat if you define a lot).

You also might want to:

  • create a function which takes a list of fields, looks up the groups in the groups_map, then selects these and concatenates the results so you get the resulting frame (this is essentially what select_as_multiple does). This way the structure would be pretty transparent to you.
  • indexes on certain data columns (makes row-subsetting much faster).
  • enable compression.

Let me know when you have questions!

Unmitigated
  • 76,500
  • 11
  • 62
  • 80
Jeff
  • 125,376
  • 21
  • 220
  • 187
  • 5
    Thanks for the links. The second link makes me a bit worried that I can't append new columns to the tables in HDFStore? Is that correct? Also, I added an example of how I would use this setup. – Zelazny7 Jan 10 '13 at 23:53
  • 5
    The actual structure in the hdf is up to you. Pytables is row oriented, with fixed columns at creation time. You cannot append columns once a table is created. However, you can create a new table indexed the same as your existing table. (see the select_as_multiple examples in the docs). This way you can create arbitrary sized objects while having pretty efficient queries. The way you use the data is key to how it should be organized on-disk. Send me an off-list e-mail with pseudo code of a more specific example. – Jeff Jan 11 '13 at 00:27
  • 1
    I have updated my question to respond to your detailed points. I will work on an example to send you off-list. Thanks! – Zelazny7 Jan 11 '13 at 04:29
  • I have added a couple of more questions about your ata in my answer – Jeff Jan 11 '13 at 12:58
  • I've addressed your new questions. I will have a proper, pseudocode example for you this weekend. Thanks! – Zelazny7 Jan 11 '13 at 15:32
  • did u have a chance to try this? – Jeff Jan 15 '13 at 03:26
  • I've just got the basic frame-work set up with a toy dataset. I think it will work nicely, though. I hope to have some time to try both answers with a real dataset, but my current workload necessitates the use of what I know best, SAS. Thanks for your response and help. The topic of "large" data is one I think many people have to deal with. With SAS as popular as it is in the business world, I'm surprised more pandas users haven't asked this kind of question. – Zelazny7 Jan 15 '13 at 03:39
  • great! would love to here of your experiences when u have run at full data - pls post on pydata on google groups! – Jeff Jan 15 '13 at 09:54
  • Is there any reason I couldn't just transpose a dataframe, add it to an HDFStore, and then index it by the "column" names? This would allow me to access only the "columns" (in the form of rows) that I need, transpose them back, and then append any new ones I create. Is there a disadvantage to having a very wide table in an HDFStore? – Zelazny7 Jan 21 '13 at 01:58
  • I took a look at my notes on HDF5 and using the where() on a many result really was slow (just matching an indexed key). Everything else it did pretty good on. Jeff can you recommend a method to optimize where? Thanks. – brian_the_bungler Jan 22 '13 at 20:33
  • sorry....didn't see your guys comments....easiest to just send me mail: jeff@reback.net – Jeff Jan 31 '13 at 22:55
  • ah cripes, my edit made in CW, will flag to see if a mod make it ordinary again... – Andy Hayden Jun 08 '13 at 09:49
  • For Jeff and @Zelazny7: I have a very similar worklow using SAS and I started reading myself into HDF5. A general question I have is how to optimally get the SAS file into hdf5 format and how to change the data types for more efficient computation and storage. For example exposure values are floats but really could be integers or some strings are very long and could be truncated etc. When and how would I do these kind of type conversions, still in SAS when creating some intermediary file, or while loading it into hdf5 format or when already in hdf5 format? – Triamus Oct 17 '14 at 11:52
  • here is an issue about this: https://github.com/pydata/pandas/issues/4052; best bet is prob to export to csv and chunk read and convert - not ideal – Jeff Oct 17 '14 at 12:20
  • I've recently spent time assessing how to use pandas in an "out of core" manner just like SAS. This is very helpful. I would be interested in hearing your thoughts on comparing the hdf5 approach you outline here, to dumping (chunk by chunk) the file (a csv file for example) in a SQLite3 table. I don't use pandas to_sql but rather dump it using the sqlite3 module so that I can define the beginning and end to a transaction. This speeds up the dumping into a physical file. I can then use read_sql to call it into pandas. I can also add columns (though not drop or rename) and append observations. – fpes May 11 '15 at 22:00
  • 14
    @Jeff, with Pandas being at 0.17.x now have the issues outlined above been resolved in Pandas? – ctrl-alt-delete Jan 13 '16 at 09:33
  • 10
    @Jeff keen on adding a quick update on your answer to promote dask? – Zeugma Sep 09 '16 at 18:10
168

I think the answers above are missing a simple approach that I've found very useful.

When I have a file that is too large to load in memory, I break up the file into multiple smaller files (either by row or cols)

Example: In case of 30 days worth of trading data of ~30GB size, I break it into a file per day of ~1GB size. I subsequently process each file separately and aggregate results at the end

One of the biggest advantages is that it allows parallel processing of the files (either multiple threads or processes)

The other advantage is that file manipulation (like adding/removing dates in the example) can be accomplished by regular shell commands, which is not be possible in more advanced/complicated file formats

This approach doesn't cover all scenarios, but is very useful in a lot of them

user1827356
  • 6,764
  • 2
  • 21
  • 30
  • 48
    Agreed. With all the hype, it's easy to forget that [command-line tools can be 235x faster than a Hadoop cluster](http://aadrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html) – zelusp Oct 06 '16 at 21:01
  • 3
    Updated link: https://adamdrake.com/command-line-tools-can-be-235x-faster-than-your-hadoop-cluster.html – amin_nejad Sep 25 '20 at 23:25
122

There is now, two years after the question, an 'out-of-core' pandas equivalent: dask. It is excellent! Though it does not support all of pandas functionality, you can get really far with it. Update: in the past two years it has been consistently maintained and there is substantial user community working with Dask.

And now, four years after the question, there is another high-performance 'out-of-core' pandas equivalent in Vaex. It "uses memory mapping, zero memory copy policy and lazy computations for best performance (no memory wasted)." It can handle data sets of billions of rows and does not store them into memory (making it even possible to do analysis on suboptimal hardware).

Private
  • 2,626
  • 1
  • 22
  • 39
  • 7
    and for a fully worked out example with dask, just have a look here http://stackoverflow.com/questions/37979167/how-to-parallelize-many-fuzzy-string-comparisons-using-apply-in-pandas – ℕʘʘḆḽḘ Feb 09 '17 at 15:58
  • 1
    Depending on your data it makes sense to take a look into [pystore](https://github.com/ranaroussi/pystore). It relies on `dask`. – gies0r May 01 '20 at 20:54
  • 4
    Is it always "out of core"? (i.e. not RAM intensive?). If you don't have a cluster at hand, Dask is not a good solution, IMHO. Quoting from the [Dask documentation itself](https://docs.dask.org/en/latest/spark.html): *"If you are looking to manage a terabyte or less of tabular CSV or JSON data, then you should forget both Spark and Dask and use Postgres or MongoDB."* – Michele Piccolini Jul 30 '20 at 15:51
71

If your datasets are between 1 and 20GB, you should get a workstation with 48GB of RAM. Then Pandas can hold the entire dataset in RAM. I know its not the answer you're looking for here, but doing scientific computing on a notebook with 4GB of RAM isn't reasonable.

rjurney
  • 4,824
  • 5
  • 41
  • 62
  • 13
    "doing scientific computing on a notebook with 4GB of RAM isn't reasonable" Define reasonable. I think UNIVAC would take a different view. http://arstechnica.com/tech-policy/2011/09/univac-the-troubled-life-of-americas-first-computer/ – william_grisaitis Aug 26 '15 at 14:42
  • 3
    Agreed! try to continue working in memory even if it costs $$ up front. If your work leads to a financial return, then over time, you will recup expenses through your increased efficiency. – ansonw Nov 10 '17 at 17:57
  • 5
    Doing scientific computing on a workstation with 48GB of RAM isn't reasonable. – Yaroslav Nikitenko Apr 06 '19 at 06:52
  • 6
    @YaroslavNikitenko An r4.2xlarge with 61GB/RAM is $.532/hour. What kind of scientific computing are you doing that isn't that valuable? Sounds unusual, if not unreasonable. – rjurney Apr 06 '19 at 22:39
  • 5
    @rjurney sorry, maybe I should had deleted my comment. Your judgement on "unreasonable" scientific computer seems very subjective. I do my scientific computations for years on laptops, and that seems enough for me, because most of the time I write code. My algorithms are much more difficult from programming point of view than from computational one. Also I'm pretty sure that to write scalable algorithms one should not rely on current hardware limitations. Your comment on other people's computing may sound a bit offensive (apart from subjectivity), would you mind deleting these few words? – Yaroslav Nikitenko Apr 07 '19 at 06:05
  • 3
    Subjective and unhelpful non-answer to the question. There might be an economic argument to this, which would depend on whether the number of executions x cpu time cost exceeds the upgrade cost. This approach can only scale with hardware limitations instead of data size. Also: so many glittering achievements in data science have been made on researchers’ laptops! Also, what about academics, students and startups who don’t have money to pour on massive workstations?! – thclark Jun 06 '19 at 14:22
  • 2
    @thclark You can optimize to hell and back if you want, but scientific computing with large datasets is most easily accomplished with a larger computer... so long as it will fit in RAM. If not you need a distributed system, but he does not have that problem so I did not give that answer. As to money, you don't need to pour money into expensive workstations. Just rent one for an hour at a time when you need it. You'll be happier and more productive for it! – rjurney Jun 06 '19 at 21:26
66

I know this is an old thread but I think the Blaze library is worth checking out. It's built for these types of situations.

From the docs:

Blaze extends the usability of NumPy and Pandas to distributed and out-of-core computing. Blaze provides an interface similar to that of the NumPy ND-Array or Pandas DataFrame but maps these familiar interfaces onto a variety of other computational engines like Postgres or Spark.

Edit: By the way, it's supported by ContinuumIO and Travis Oliphant, author of NumPy.

chishaku
  • 4,577
  • 3
  • 25
  • 33
  • Another library that might be worth looking at is GraphLab Create: It has an efficient DataFrame-like structure that is not limited by memory capacity. http://blog.dato.com/interactively-analyzing-terabytes-of-data-using-graphlab-create – waterproof Apr 22 '15 at 16:17
59

This is the case for pymongo. I have also prototyped using sql server, sqlite, HDF, ORM (SQLAlchemy) in python. First and foremost pymongo is a document based DB, so each person would be a document (dict of attributes). Many people form a collection and you can have many collections (people, stock market, income).

pd.dateframe -> pymongo Note: I use the chunksize in read_csv to keep it to 5 to 10k records(pymongo drops the socket if larger)

aCollection.insert((a[1].to_dict() for a in df.iterrows()))

querying: gt = greater than...

pd.DataFrame(list(mongoCollection.find({'anAttribute':{'$gt':2887000, '$lt':2889000}})))

.find() returns an iterator so I commonly use ichunked to chop into smaller iterators.

How about a join since I normally get 10 data sources to paste together:

aJoinDF = pandas.DataFrame(list(mongoCollection.find({'anAttribute':{'$in':Att_Keys}})))

then (in my case sometimes I have to agg on aJoinDF first before its "mergeable".)

df = pandas.merge(df, aJoinDF, on=aKey, how='left')

And you can then write the new info to your main collection via the update method below. (logical collection vs physical datasources).

collection.update({primarykey:foo},{key:change})

On smaller lookups, just denormalize. For example, you have code in the document and you just add the field code text and do a dict lookup as you create documents.

Now you have a nice dataset based around a person, you can unleash your logic on each case and make more attributes. Finally you can read into pandas your 3 to memory max key indicators and do pivots/agg/data exploration. This works for me for 3 million records with numbers/big text/categories/codes/floats/...

You can also use the two methods built into MongoDB (MapReduce and aggregate framework). See here for more info about the aggregate framework, as it seems to be easier than MapReduce and looks handy for quick aggregate work. Notice I didn't need to define my fields or relations, and I can add items to a document. At the current state of the rapidly changing numpy, pandas, python toolset, MongoDB helps me just get to work :)

ali_m
  • 71,714
  • 23
  • 223
  • 298
brian_the_bungler
  • 991
  • 2
  • 7
  • 12
  • Hi, I'm playing around with your example as well and I run into this error when trying to insert into a database: `In [96]: test.insert((a[1].to_dict() for a in df.iterrows())) --------------- InvalidDocument: Cannot encode object: 0`. Any ideas what might be wrong? My dataframe consists of all int64 dtypes and is very simple. – Zelazny7 Jan 16 '13 at 02:53
  • 2
    Yeah i did the same for a simple range DF and the int64 from numpy seems to bother pymongo. All the data I have played with converts from CSV (vs artificially via range()) and has types long and hence no issues. In numpy you can convert but I do see that as detracting. I must admit the 10.1 items for HDF look exciting. – brian_the_bungler Jan 17 '13 at 22:01
55

One trick I found helpful for large data use cases is to reduce the volume of the data by reducing float precision to 32-bit. It's not applicable in all cases, but in many applications 64-bit precision is overkill and the 2x memory savings are worth it. To make an obvious point even more obvious:

>>> df = pd.DataFrame(np.random.randn(int(1e8), 5))
>>> df.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float64(5)
memory usage: 3.7 GB

>>> df.astype(np.float32).info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 100000000 entries, 0 to 99999999
Data columns (total 5 columns):
...
dtypes: float32(5)
memory usage: 1.9 GB
Sociopath
  • 13,068
  • 19
  • 47
  • 75
ytsaig
  • 3,267
  • 3
  • 23
  • 27
  • Generally speaking I think you should give a tought to every column type and have a dict that handle them consistently. This allows further downcasting (to small int, or even better, category). Depending on the purpose you may even use some 'trick' like changing the unit you use (using k$ might help you downcasting everything to int16) or grouping things into category. Ideally, that should be done before the first export. – Lucas Morin Oct 24 '20 at 09:08
49

I spotted this a little late, but I work with a similar problem (mortgage prepayment models). My solution has been to skip the pandas HDFStore layer and use straight pytables. I save each column as an individual HDF5 array in my final file.

My basic workflow is to first get a CSV file from the database. I gzip it, so it's not as huge. Then I convert that to a row-oriented HDF5 file, by iterating over it in python, converting each row to a real data type, and writing it to a HDF5 file. That takes some tens of minutes, but it doesn't use any memory, since it's only operating row-by-row. Then I "transpose" the row-oriented HDF5 file into a column-oriented HDF5 file.

The table transpose looks like:

def transpose_table(h_in, table_path, h_out, group_name="data", group_path="/"):
    # Get a reference to the input data.
    tb = h_in.getNode(table_path)
    # Create the output group to hold the columns.
    grp = h_out.createGroup(group_path, group_name, filters=tables.Filters(complevel=1))
    for col_name in tb.colnames:
        logger.debug("Processing %s", col_name)
        # Get the data.
        col_data = tb.col(col_name)
        # Create the output array.
        arr = h_out.createCArray(grp,
                                 col_name,
                                 tables.Atom.from_dtype(col_data.dtype),
                                 col_data.shape)
        # Store the data.
        arr[:] = col_data
    h_out.flush()

Reading it back in then looks like:

def read_hdf5(hdf5_path, group_path="/data", columns=None):
    """Read a transposed data set from a HDF5 file."""
    if isinstance(hdf5_path, tables.file.File):
        hf = hdf5_path
    else:
        hf = tables.openFile(hdf5_path)

    grp = hf.getNode(group_path)
    if columns is None:
        data = [(child.name, child[:]) for child in grp]
    else:
        data = [(child.name, child[:]) for child in grp if child.name in columns]

    # Convert any float32 columns to float64 for processing.
    for i in range(len(data)):
        name, vec = data[i]
        if vec.dtype == np.float32:
            data[i] = (name, vec.astype(np.float64))

    if not isinstance(hdf5_path, tables.file.File):
        hf.close()
    return pd.DataFrame.from_items(data)

Now, I generally run this on a machine with a ton of memory, so I may not be careful enough with my memory usage. For example, by default the load operation reads the whole data set.

This generally works for me, but it's a bit clunky, and I can't use the fancy pytables magic.

Edit: The real advantage of this approach, over the array-of-records pytables default, is that I can then load the data into R using h5r, which can't handle tables. Or, at least, I've been unable to get it to load heterogeneous tables.

Johann Hibschman
  • 1,997
  • 1
  • 16
  • 17
  • would you mind sharing with me some of your code? I am interested in how you load the data from some flat text format without knowing the data types before pushing to pytables. Also, it looks like you only work with data of one type. Is that correct? – Zelazny7 Mar 27 '13 at 02:01
  • 1
    First of all, I assume I know the types of the columns before loading, rather than trying to guess from the data. I save a JSON "data spec" file with the column names and types and use that when processing the data. (The file is usually some awful BCP output without any labels.) The data types I use are strings, floats, integers, or monthly dates. I turn the strings into ints by saving an enumeration table and convert the dates into ints (months past 2000), so I'm just left with ints and floats in my data, plus the enumeration. I save the floats as float64 now, but I experimented with float32. – Johann Hibschman Mar 28 '13 at 16:34
  • 1
    if you have time, pls give this a try for external compat with R: http://pandas.pydata.org/pandas-docs/dev/io.html#external-compatibility, and if you have difficulty, maybe we can tweak it – Jeff Apr 01 '13 at 16:50
  • I'll try, if I can. rhdf5 is a pain, since it's a bioconductor package, rather than just being on CRAN like h5r. I'm at the mercy of our technical architecture team, and there was some issue with rhdf5 last time I asked for it. In any case, it just seems a mistake to go row-oriented rather than column-oriented with an OLAP store, but now I'm rambling. – Johann Hibschman Apr 01 '13 at 20:09
37

As noted by others, after some years an 'out-of-core' pandas equivalent has emerged: dask. Though dask is not a drop-in replacement of pandas and all of its functionality it stands out for several reasons:

Dask is a flexible parallel computing library for analytic computing that is optimized for dynamic task scheduling for interactive computational workloads of “Big Data” collections like parallel arrays, dataframes, and lists that extend common interfaces like NumPy, Pandas, or Python iterators to larger-than-memory or distributed environments and scales from laptops to clusters.

Dask emphasizes the following virtues:

  • Familiar: Provides parallelized NumPy array and Pandas DataFrame objects
  • Flexible: Provides a task scheduling interface for more custom workloads and integration with other projects.
  • Native: Enables distributed computing in Pure Python with access to the PyData stack.
  • Fast: Operates with low overhead, low latency, and minimal serialization necessary for fast numerical algorithms
  • Scales up: Runs resiliently on clusters with 1000s of cores Scales down: Trivial to set up and run on a laptop in a single process
  • Responsive: Designed with interactive computing in mind it provides rapid feedback and diagnostics to aid humans

and to add a simple code sample:

import dask.dataframe as dd
df = dd.read_csv('2015-*-*.csv')
df.groupby(df.user_id).value.mean().compute()

replaces some pandas code like this:

import pandas as pd
df = pd.read_csv('2015-01-01.csv')
df.groupby(df.user_id).value.mean()

and, especially noteworthy, provides through the concurrent.futures interface a general infrastructure for the submission of custom tasks:

from dask.distributed import Client
client = Client('scheduler:port')

futures = []
for fn in filenames:
    future = client.submit(load, fn)
    futures.append(future)

summary = client.submit(summarize, futures)
summary.result()
wp78de
  • 18,207
  • 7
  • 43
  • 71
  • I have added this answer since the post of @Private shows up regularly on the suggested for deletion list for content and length. – wp78de Nov 22 '17 at 03:57
  • Dask is great unless you need multiindexing. Lack of multiindexing as of time of writing is a major problem. – misantroop Sep 08 '20 at 04:41
27

It is worth mentioning here Ray as well,
it's a distributed computation framework, that has it's own implementation for pandas in a distributed way.

Just replace the pandas import, and the code should work as is:

# import pandas as pd
import ray.dataframe as pd

# use pd as usual

can read more details here:

https://rise.cs.berkeley.edu/blog/pandas-on-ray/


Update: the part that handles the pandas distribution, has been extracted to the modin project.

the proper way to use it is now is:

# import pandas as pd
import modin.pandas as pd
lev
  • 3,986
  • 4
  • 33
  • 46
21

One more variation

Many of the operations done in pandas can also be done as a db query (sql, mongo)

Using a RDBMS or mongodb allows you to perform some of the aggregations in the DB Query (which is optimized for large data, and uses cache and indexes efficiently)

Later, you can perform post processing using pandas.

The advantage of this method is that you gain the DB optimizations for working with large data, while still defining the logic in a high level declarative syntax - and not having to deal with the details of deciding what to do in memory and what to do out of core.

And although the query language and pandas are different, it's usually not complicated to translate part of the logic from one to another.

Ophir Yoktan
  • 8,149
  • 7
  • 58
  • 106
14

Consider Ruffus if you go the simple path of creating a data pipeline which is broken down into multiple smaller files.

Golf Monkey
  • 175
  • 2
  • 7
13

I'd like to point out the Vaex package.

Vaex is a python library for lazy Out-of-Core DataFrames (similar to Pandas), to visualize and explore big tabular datasets. It can calculate statistics such as mean, sum, count, standard deviation etc, on an N-dimensional grid up to a billion (109) objects/rows per second. Visualization is done using histograms, density plots and 3d volume rendering, allowing interactive exploration of big data. Vaex uses memory mapping, zero memory copy policy and lazy computations for best performance (no memory wasted).

Have a look at the documentation: https://vaex.readthedocs.io/en/latest/ The API is very close to the API of pandas.

Rob
  • 3,418
  • 1
  • 19
  • 27
11

I recently came across a similar issue. I found simply reading the data in chunks and appending it as I write it in chunks to the same csv works well. My problem was adding a date column based on information in another table, using the value of certain columns as follows. This may help those confused by dask and hdf5 but more familiar with pandas like myself.

def addDateColumn():
"""Adds time to the daily rainfall data. Reads the csv as chunks of 100k 
   rows at a time and outputs them, appending as needed, to a single csv. 
   Uses the column of the raster names to get the date.
"""
    df = pd.read_csv(pathlist[1]+"CHIRPS_tanz.csv", iterator=True, 
                     chunksize=100000) #read csv file as 100k chunks

    '''Do some stuff'''

    count = 1 #for indexing item in time list 
    for chunk in df: #for each 100k rows
        newtime = [] #empty list to append repeating times for different rows
        toiterate = chunk[chunk.columns[2]] #ID of raster nums to base time
        while count <= toiterate.max():
            for i in toiterate: 
                if i ==count:
                    newtime.append(newyears[count])
            count+=1
        print "Finished", str(chunknum), "chunks"
        chunk["time"] = newtime #create new column in dataframe based on time
        outname = "CHIRPS_tanz_time2.csv"
        #append each output to same csv, using no header
        chunk.to_csv(pathlist[2]+outname, mode='a', header=None, index=None)
timpjohns
  • 599
  • 4
  • 13
0

The parquet file format is ideal for the use case you described. You can efficiently read in a specific subset of columns with pd.read_parquet(path_to_file, columns=["foo", "bar"])

https://pandas.pydata.org/docs/reference/api/pandas.read_parquet.html

user2957943
  • 173
  • 2
  • 8
-2

At the moment I am working "like" you, just on a lower scale, which is why I don't have a PoC for my suggestion.

However, I seem to find success in using pickle as caching system and outsourcing execution of various functions into files - executing these files from my commando / main file; For example i use a prepare_use.py to convert object types, split a data set into test, validating and prediction data set.

How does your caching with pickle work? I use strings in order to access pickle-files that are dynamically created, depending on which parameters and data sets were passed (with that i try to capture and determine if the program was already run, using .shape for data set, dict for passed parameters). Respecting these measures, i get a String to try to find and read a .pickle-file and can, if found, skip processing time in order to jump to the execution i am working on right now.

Using databases I encountered similar problems, which is why i found joy in using this solution, however - there are many constraints for sure - for example storing huge pickle sets due to redundancy. Updating a table from before to after a transformation can be done with proper indexing - validating information opens up a whole other book (I tried consolidating crawled rent data and stopped using a database after 2 hours basically - as I would have liked to jump back after every transformation process)

I hope my 2 cents help you in some way.

Greetings.

TiRoX
  • 31
  • 7