[python-package] Add support for PyArrow-backed Pandas DataFrames#7263
[python-package] Add support for PyArrow-backed Pandas DataFrames#7263maxzw wants to merge 2 commits into
Conversation
|
@jameslamb I'd appreciate your feedback on this draft PR! |
|
I raised a PR(#7264) that actually addresses categorical handling for polars dataframes and while you raise a good point about the numpy copy it might be more performant if import polars as pl
import pandas as pd
import pyarrow as pa
# 1. Ensure Pandas 2.0+ and pyarrow are installed.
# 2. Create Pandas DataFrame backed by Arrow
df_pd = pd.DataFrame({'a': [1, 2], 'b': [3, 4]}).convert_dtypes(dtype_backend="pyarrow")
# 3. Use pl.from_pandas()
# This uses Arrow memory directly if possible (zero-copy).
pl_df = pl.from_pandas(df_pd)Additionally, |
|
@Ic3fr0g that does work, but I don't think polars should be a dependency for users that just use pandas 2.0. |
|
Just letting you know, looks like See https://github.com/lightgbm-org/LightGBM/pull/7268/changes#r3223494907 for a workaround and follow #7269 for a resolution. |
e42f4f0 to
035f680
Compare
|
Hey @maxzw! I have recently opened #7275 which will improve the Arrow integration of LightGBM. Once that is merged, I would like to repurpose this PR (feel free to open a new one entirely) as follows:
Overall, this should make the PR much more straightforward. |
Hi @borchero thanks for the heads-up. I'll close this one and see - depending on the referenced PR's - if I can be of help filling the remaining gaps. |
Problem
Fixes #5739
LightGBM currently converts all pandas DataFrames to NumPy arrays during Dataset construction, even when the DataFrame uses PyArrow-backed columns (
ArrowDtype). This forces an unnecessary copy of data that's already in Arrow's columnar format, missing an opportunity for zero-copy data transfer to the C++ backend.With pandas 2.0+ increasingly adopting PyArrow as the default backend for better memory efficiency and interoperability, users working with Arrow-backed DataFrames face a performance penalty. This PR enables LightGBM to detect and leverage PyArrow-backed columns, maintaining data in the Arrow format through the entire pipeline.
Approach
The key design principle was to keep pandas-specific processing (categorical handling, type validation) in the pandas-specific code path while cleanly passing Arrow tables downstream.
Integration Point: Modified
_data_from_pandas()to detect PyArrow-backed columns and convert the entire DataFrame to an Arrow Table instead of a NumPy array. This keeps all existing categorical feature logic intact while letting the downstream C++ interface naturally handle Arrow tables via the existing_create_from_arrow_table()path.Zero-Copy Strategy: The implementation handles DataFrames with mixed dtypes (Arrow + NumPy + nullable extension types) by extracting underlying buffers directly where possible, avoiding intermediate copies. Each column type is handled optimally:
.__arrow_array__().valuesfor a zero-copy viewOnly dtypes already supported by LightGBM's Arrow interface (int, float, bool) are allowed, ensuring consistency with the existing Arrow code path.
Implementation note: An alternative approach was considered that would use
pa.Table.from_pandas()for all-Arrow DataFrames:However, benchmarking showed this provided no meaningful speedup over the unified column-by-column implementation, so the latter approach was chosen.
Changes
Core Logic (basic.py)
_pandas_to_arrow_table()(new): Converts pandas DataFrames to Arrow Tables with zero-copy optimizations. Uses a unified implementation that handles mixed dtypes efficiently:.__arrow_array__()).valuesfor zero-copy viewpa.Table.from_arrays()for consistent performance across all DataFrame types_data_from_pandas(): Updated return type fromnp.ndarraytoUnion[np.ndarray, pa.Table]. Early-exits with Arrow table when any PyArrow-backed columns detected, preserving all existing categorical logic._check_for_bad_arrow_dtypes()(new): Centralized validation for Arrow type compatibility, replacing duplicated checks in Dataset and _InnerPredictor._is_pd_arrow_dtype(),_is_arrow_backed_pd_series(),_extract_arrow_array()(new): Helper functions for detecting and extracting Arrow arrays from pandas Series.Metadata field support: Extended
set_label(),set_weight(),set_group(),set_position()to accept PyArrow-backed Series, maintaining API consistency.Compatibility Layer (compat.py)
pd.ArrowDtypeandpa.DataTypewith fallbacksTests
Basic Dataset functionality (test_basic.py):
Sklearn integration (test_sklearn.py):
Benchmarks
All benchmarks ran 50 iterations per configuration on datasets ranging from 10K to 1M rows. Four scenarios tested:
Dataset Construction Time
PyArrow-backed DataFrames provide speedups up to 43% in dataset construction for large datasets (50K+ rows). Minimal impact for small datasets (10K rows: ~6%).
Benchmark code and logs
The benchmark below was ran on a 16GB MacBook Pro M2 Pro.
Logs:
End-to-End Training Time
PyArrow provides speedups up to 10% for end-to-end training on large datasets (500K-1M rows). Minimal impact for small to medium datasets.
Benchmark code and logs
The benchmark below was ran on a 16GB MacBook Pro M2 Pro.
Logs:
Sklearn Integration
PyArrow has minimal impact on sklearn integration performance, with speedups up to 11% for large datasets (500K rows). Performance is within ±2% for most dataset sizes.
Benchmark code and logs
The benchmark below was ran on a 16GB MacBook Pro M2 Pro.
Logs:
Compatibility
Requires
pyarrowandcffiinstalled (same as existing Arrow support).Open Questions