Skip to content

Commit 078029c

Browse files
committed
pandas: handle non-contiguous memory
Use the numpy backend to ensure all numpy arrays extracted from pandas columns are C-contiguous before scanning. Non-contiguous arrays can arise from DataFrames backed by views into larger 2D arrays (e.g. pd.DataFrame(arr2d), transpose, concat, slicing). Previously the scan used memcpy with sizeof(T) stride which reads wrong data on non-contiguous layouts.
1 parent 984f56c commit 078029c

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

src_cpp/pandas/pandas_bind.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ namespace lbug {
77

88
using namespace lbug::common;
99

10+
// Ensure a numpy array is C-contiguous so that the scan code can safely use
11+
// memcpy on the raw data pointer. Non-contiguous arrays can arise from
12+
// DataFrame columns backed by views into larger 2D arrays (e.g. after
13+
// transpose, concat, or slicing).
14+
static py::array ensureContiguous(py::object obj) {
15+
auto arr = py::array(obj);
16+
if (!arr.attr("flags").attr("c_contiguous").cast<bool>()) {
17+
arr = py::module_::import("numpy").attr("ascontiguousarray")(arr);
18+
}
19+
return arr;
20+
}
21+
1022
struct PandasBindColumn {
1123
public:
1224
PandasBindColumn(py::handle name, py::handle type, py::object column)
@@ -54,25 +66,24 @@ static common::LogicalType bindColumn(PandasBindColumn& bindColumn,
5466
}
5567

5668
if (bindData->npType.type == NumpyNullableType::FLOAT_16) {
57-
auto pandasArray = column.attr("array");
5869
bindData->pandasCol =
59-
std::make_unique<PandasNumpyColumn>(py::array(column.attr("to_numpy")("float32")));
70+
std::make_unique<PandasNumpyColumn>(ensureContiguous(column.attr("to_numpy")("float32")));
6071
bindData->npType.type = NumpyNullableType::FLOAT_32;
6172
columnType = NumpyTypeUtils::numpyToLogicalType(bindData->npType);
6273
} else {
6374
auto pandasArray = column.attr("array");
6475
if (py::hasattr(pandasArray, "_data")) {
6576
// This means we can access the numpy array directly.
6677
bindData->pandasCol =
67-
std::make_unique<PandasNumpyColumn>(column.attr("array").attr("_data"));
78+
std::make_unique<PandasNumpyColumn>(ensureContiguous(column.attr("array").attr("_data")));
6879
} else if (py::hasattr(pandasArray, "asi8")) {
6980
// This is a datetime object, has the option to get the array as int64_t's.
7081
bindData->pandasCol =
71-
std::make_unique<PandasNumpyColumn>(py::array(pandasArray.attr("asi8")));
82+
std::make_unique<PandasNumpyColumn>(ensureContiguous(pandasArray.attr("asi8")));
7283
} else {
7384
// Otherwise we have to get it through 'to_numpy()'.
7485
bindData->pandasCol =
75-
std::make_unique<PandasNumpyColumn>(py::array(column.attr("to_numpy")()));
86+
std::make_unique<PandasNumpyColumn>(ensureContiguous(column.attr("to_numpy")()));
7687
}
7788
columnType = NumpyTypeUtils::numpyToLogicalType(bindData->npType);
7889
}

0 commit comments

Comments
 (0)