From 0d3b91bd5b0c5c93ec6f877f12848bc99591d453 Mon Sep 17 00:00:00 2001 From: Ralf Gommers Date: Wed, 12 May 2021 20:27:58 +0200 Subject: [PATCH] Add blog post on dataframe interchange protocol RFC --- content/blog/dataframe_protocol_RFC.md | 220 +++++++++++++++++++ static/images/dataframe_conceptual_model.png | Bin 0 -> 40752 bytes 2 files changed, 220 insertions(+) create mode 100644 content/blog/dataframe_protocol_RFC.md create mode 100644 static/images/dataframe_conceptual_model.png diff --git a/content/blog/dataframe_protocol_RFC.md b/content/blog/dataframe_protocol_RFC.md new file mode 100644 index 0000000..bc92375 --- /dev/null +++ b/content/blog/dataframe_protocol_RFC.md @@ -0,0 +1,220 @@ ++++ +date = "2021-08-24" +author = "Ralf Gommers" +title = "Towards dataframe interoperability" +tags = ["APIs", "standard", "consortium", "dataframes", "community"] +categories = ["Consortium", "Standardization"] +description = "An RFC for a dataframe interchange protocol" +draft = false +weight = 40 ++++ + + +In the PyData ecosystem we have a large number of dataframe libraries as of +today, each with their own strengths and weaknesses. Pandas is the most +popular library today. Other libraries offer significant capabilities beyond +what it provides though - impressive performance gains for Vaex (CPU) and +cuDF (GPU), distributed dataframes for Modin and Dask, or leveraging Spark as +an execution engine for Koalas. For downstream library authors, it would be +powerful to be able to work with all these libraries. Which right now is +quite difficult, and therefore in practice most library authors choose to +focus only on Pandas. + +The first step to improve this situation is to use a "data interchange +protocol", which will allow converting one type of dataframe into another, as +well as inspect the dataframe for basic properties ("how many columns does it +have?", "what are the column names?", "what are the dtypes for a given +column?") and convert only subsets of it. + +We are happy to release a Request for Comments (RFC) today, containing both a +design document with purpose, scope and requirements for such a dataframe +interchange protocol, as well as a prototype design: +[documentation](https://data-apis.org/dataframe-protocol/latest/index.html), +[repository](https://github.com/data-apis/dataframe-api). + +We note that an interchange protocol is not a completely new idea: for arrays +we have had such protocols for a long time, e.g., `__array_interface__`, the +buffer protocol (PEP 3118), `__cuda_array_interface__` and DLPack. The +conversation about a dataframe interchange protocol was started by Gael +Varoquaux last year in [this Discourse +thread](https://discuss.ossdata.org/t/a-dataframe-protocol-for-the-pydata-ecosystem/267). +In response Wes McKinney sketched up an initial prototype +[here](https://github.com/wesm/dataframe-protocol/pull/1). There were a lot +of good ideas in that initial conversation and prototype, however it was +clear that it was a complex enough topic that a more thorough approach +including collecting requirements and use cases from a large set of +stakeholders was needed. The RFC we're announcing in this blog post is the +result of taking that approach, and hopefully will be the starting point for +implementations in all Python dataframe libraries. + +_We want to emphasize that this is not a full dataframe API; the only +attribute added to the dataframe class/object of a library will be +`__dataframe__`. It is aimed at library authors, not at end users._ + + +## What is a "dataframe" anyway? + +Defining what a dataframe _is_ turns out to be surprisingly difficult +exercise. For example, can column named be integer or only strings, and must +they be unique? Are row labels required, optional, or not a thing? Should +there be any restriction on how data is stored inside a dataframe? Does it +have other properties, like row-column symmetry, or support for certain +operations? + +For the purposes of data interchange, we need to describe a dataframe both +conceptually, and in terms of data representation in memory so that another +library can interpret that data. Furthermore, we want to impose as few extra +constraints as possible. Here is our working definition: _A dataframe is an +ordered collection of columns, which are conceptually 1-D arrays with a dtype +and missing data support. A column has a name, which is a unique string. A +dataframe or a column may be "chunked", meaning its data is not contiguous in +memory._ + +![Conceptual model of a dataframe, with columns (possibly containing missing data), and chunks](/images/dataframe_conceptual_model.png) + +For more on the conceptual model, and on requirements that a dataframe +protocol must fulfill, see [this design +document](https://data-apis.org/dataframe-protocol/latest/design_requirements.html). + + +## Key design choices + +Given the goals and requirements we had for the protocol, there were still a +number of design choices to make. The single most important choice is: does +the protocol offer a description of how data is laid out in memory, or does +it offer a way (or multiple ways) of exporting data in a given format, e.g. a +column as an Apache Arrow array or a NumPy array. + +The choice we made here in [the current +prototype](https://github.com/data-apis/dataframe-api/tree/main/protocol) is: +do not assume a particular implementation, describe memory down to the level of +buffers (=contiguous, 1-D blocks of memory). And at that buffer level, we can +make the connection between this dataframe protocol and the +[array API standard via `__dlpack__`](https://data-apis.org/array-api/latest/design_topics/data_interchange.html). + + +### Similarity (and synergy?) with the Arrow C Data Interface + +When looking at the requirements and native in-memory formats of all +prominent dataframe libraries, we found that the Arrow C Data Interface is +pretty close to meeting all the requirements. So a natural question is: can +we use that interface, and standardize a Python API on top of it? + +There are a couple of things in the current Arrow C Data Interface that +didn't quite match everyone's needs. Most importantly, the Arrow C Data +Interface does not have device support (e.g., GPUs). Other issues (or wishes) +are: +- The "deleter", which releases memory when it's no longer needed, lives at + the column level in Arrow. Multiple people expressed the desire for more + granular control. It seems more natural and performant to have the deleter + at the buffer level. +- Allowing a column to have its data split over different devices, e.g. part + of the data lives on CPU and part on GPU (a necessity if the data doesn't + fit in GPU memory). +- Arrow supports masks, for null/missing values, as a bit mask. NumPy doesn't + have bit masks, and boolean masks are normally one byte per value. This is + a smaller issue though, because it can be solved via a convention like + using (e.g.) a regular `int8` column with a certain name. + +Compared to the similaries between the two protocols, the differences are +relatively minor. And a lot of work has already gone into the Arrow C Data +Interface, hence we are interested in exploring if we can contribute the +identified improvements back to Apache Arrow. That would potentially let us +support, for example, an `__arrow_column__` attribute at the column level in +Python, which would save dataframe libraries that already use Apache Arrow a +significant amount of implementation work. + + +### A standard dataframe creation function + +Also in the analogy to the array API standard, we are proposing a single new +function, `from_dataframe`, for dataframe libraries to add in their top-level +namespace. This function will know how to construct a library-native +dataframe instance from any other dataframe object. Here is an example for +Modin: + +```python +import modin.pandas as pd + + +def somefunc(df, ...): + """ + Do something interesting with dataframe `df`. + + Parameters + ---------- + df : dataframe instance + Can be a Modin dataframe, or any other kind of dataframe supporting the `__dataframe__` protocol + """ + df_modin = pd.from_dataframe(df) + # From now on, use Modin dataframe internally + + +def somefunc2(df, col1, col2): + """ + Do something interesting with two columns from dataframe `df`. + + Parameters + ---------- + df : dataframe instance + Can be a Modin dataframe, or any other kind of dataframe supporting the `__dataframe__` protocol + col1 : str + Name of column 1 + col1 : str + Name of column 2 + """ + # This will extract just the two columns we need from `df`, and put them in + # a Modin dataframe. This is much more efficient than converting the + # (potentially very large) complete dataframe. + df_modin = pd.from_dataframe(df, cols=[col1, col2]) +``` + + +## Next steps + +This protocol is not completely done. We are releasing it now in order to get +feedback from a wider range of stakeholders. We are interested to hear about +everything from potential use cases we missed or should describe better, to +whether the API feels natural, and low-level performance/implementation +concerns or ideas for improvement. + +Today we are releasing one prototype implementation, for Pandas. Most of that +prototype can be reused for implementations in other libraries. What we'd +really like to see next is: can this be used in downstream libraries like +scikit-learn or Seaborn? Right now those accept Pandas dataframes; letting +them work with other types of dataframes is potentially quite valuable. This +is what we should see before finalizing the API and semantics of this +protocol. + + +## What about a full dataframe API? + +At the end of last year we released a full +[array API standard](https://data-apis.github.io/array-api/latest/). +So what about a full dataframe API? + +Our initial intent was to take the methodology we used for constructing the +array API, and the lessons we learned doing so, to dataframes. We found that +to be quite challenging however, due to two reasons: + +1. It turns out that dataframe library authors & end users have quite + different API design needs. Much more so than for arrays. Library authors + need clear semantics, no surprises or performance cliffs, and explicit + APIs. End users seem to want more "magic", where API calls can be chained + and basically "do the right thing". +2. For array libraries we used _API synthesis_, and based design decisions + partly on data about how often current APIs are used. This worked because + maintainers and end users are largely happy with the state of APIs for + n-dimensional arrays. Those have an almost 25-year long history, so that's + not surprising. Dataframes are much younger - Pandas was created in 2009 + and reached version 1.0 only last year. And much more is still in flux + there. Hence freezing the current state of dataframe APIs via + standardization did not seem like a good idea. + +So, what's next for a larger dataframe API? Our strategy will be to +focus on library authors as an audience, and based on the introduction of the +interchange protocol see if we can identify next pieces that are useful. And +then organically grow the size of the API, while being careful to not +standardize APIs that dataframe library maintainers are not completely +satisfied with. + diff --git a/static/images/dataframe_conceptual_model.png b/static/images/dataframe_conceptual_model.png new file mode 100644 index 0000000000000000000000000000000000000000..3643b3895786ca938efafc05611fdbfa50934186 GIT binary patch literal 40752 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 literal 0 HcmV?d00001