Skip to content

Commit ca37093

Browse files
AlenkaFtadejaCopilotrok
authored
GH-40062: [C++][Python] Conversion of Table to Arrow Tensor (#41870)
### Rationale for this change There is currently no method to convert Arrow Table to Arrow Tensor (conversion from columnar format to a contiguous block of memory). This work is a continuation of `RecordBatch::ToTensor` work, see #40058. ### What changes are included in this PR? This PR: - implements `Table::ToTensor` conversion - adds bindings to Python - adds benchmarks in C++ - removes the code in `RecordBatch::ToTensor` and uses the Table implementation (`RecordBatch::ToTensor` benchmarks checked) ### Are these changes tested? Yes, in C++ and Python. ### Are there any user-facing changes? No, it is a new feature. * GitHub Issue: #40062 Lead-authored-by: AlenkaF <frim.alenka@gmail.com> Co-authored-by: Alenka Frim <AlenkaF@users.noreply.github.com> Co-authored-by: tadeja <tadeja@users.noreply.github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Rok Mihevc <rok@mihevc.org> Signed-off-by: AlenkaF <frim.alenka@gmail.com>
1 parent 7ebe6e9 commit ca37093

12 files changed

Lines changed: 967 additions & 114 deletions

File tree

cpp/src/arrow/record_batch.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include "arrow/record_batch.h"
1919

2020
#include <algorithm>
21-
#include <cmath>
2221
#include <cstdlib>
2322
#include <memory>
2423
#include <mutex>

cpp/src/arrow/record_batch.h

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,9 @@ class ARROW_EXPORT RecordBatch {
9090
/// in the resulting struct array.
9191
Result<std::shared_ptr<StructArray>> ToStructArray() const;
9292

93-
/// \brief Convert record batch with one data type to Tensor
93+
/// \brief Convert RecordBatch to Tensor
9494
///
95-
/// Create a Tensor object with shape (number of rows, number of columns) and
96-
/// strides (type size in bytes, type size in bytes * number of rows).
97-
/// Generated Tensor will have column-major layout.
95+
/// Create a Tensor object.
9896
///
9997
/// \param[in] null_to_nan if true, convert nulls to NaN
10098
/// \param[in] row_major if true, create row-major Tensor else column-major Tensor

cpp/src/arrow/record_batch_test.cc

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -910,10 +910,11 @@ TEST_F(TestRecordBatch, ToTensorUnsupportedMissing) {
910910

911911
auto batch = RecordBatch::Make(schema, length, {a0, a1});
912912

913-
ASSERT_RAISES_WITH_MESSAGE(TypeError,
914-
"Type error: Can only convert a RecordBatch with no nulls. "
915-
"Set null_to_nan to true to convert nulls to NaN",
916-
batch->ToTensor());
913+
ASSERT_RAISES_WITH_MESSAGE(
914+
TypeError,
915+
"Type error: Can only convert a Table or RecordBatch with no "
916+
"nulls. Set null_to_nan to true to convert nulls to NaN",
917+
batch->ToTensor());
917918
}
918919

919920
TEST_F(TestRecordBatch, ToTensorEmptyBatch) {
@@ -944,10 +945,11 @@ TEST_F(TestRecordBatch, ToTensorEmptyBatch) {
944945
auto batch_no_columns =
945946
RecordBatch::Make(::arrow::schema({}), 10, std::vector<std::shared_ptr<Array>>{});
946947

947-
ASSERT_RAISES_WITH_MESSAGE(TypeError,
948-
"Type error: Conversion to Tensor for RecordBatches without "
949-
"columns/schema is not supported.",
950-
batch_no_columns->ToTensor());
948+
ASSERT_RAISES_WITH_MESSAGE(
949+
TypeError,
950+
"Type error: Conversion to Tensor for Tables or RecordBatches "
951+
"without columns/schema is not supported.",
952+
batch_no_columns->ToTensor());
951953
}
952954

953955
template <typename DataType>
@@ -1116,6 +1118,44 @@ TEST_F(TestRecordBatch, ToTensorSupportedNullToNan) {
11161118
CheckTensorRowMajor<FloatType>(tensor2_row, 18, shape, strides_2);
11171119
}
11181120

1121+
TEST_F(TestRecordBatch, ToTensorNullToNanFloat16) {
1122+
// Tensor::Equals does not yet support NaN-aware comparison for float16, so
1123+
// null slots are verified by inspecting the raw buffer directly.
1124+
const int length = 9;
1125+
1126+
auto f0 = field("f0", float16());
1127+
auto f1 = field("f1", float16());
1128+
auto schema = ::arrow::schema({f0, f1});
1129+
1130+
auto a0 = ArrayFromJSON(float16(), "[null, 2, 3, 4, 5, 6, 7, 8, 9]");
1131+
auto a1 = ArrayFromJSON(float16(), "[10, 20, 30, 40, null, 60, 70, 80, 90]");
1132+
auto batch = RecordBatch::Make(schema, length, {a0, a1});
1133+
1134+
// Column-major
1135+
ASSERT_OK_AND_ASSIGN(auto tensor,
1136+
batch->ToTensor(/*null_to_nan=*/true, /*row_major=*/false));
1137+
ASSERT_OK(tensor->Validate());
1138+
1139+
std::vector<int64_t> shape = {9, 2};
1140+
const int64_t f16_size = sizeof(uint16_t);
1141+
CheckTensor<HalfFloatType>(tensor, 18, shape, {f16_size, f16_size * shape[0]});
1142+
1143+
const auto* buf = reinterpret_cast<const uint16_t*>(tensor->raw_data());
1144+
EXPECT_TRUE(util::Float16::FromBits(buf[0]).is_nan());
1145+
EXPECT_TRUE(util::Float16::FromBits(buf[13]).is_nan());
1146+
1147+
// Row-major
1148+
ASSERT_OK_AND_ASSIGN(auto tensor_row, batch->ToTensor(/*null_to_nan=*/true));
1149+
ASSERT_OK(tensor_row->Validate());
1150+
1151+
CheckTensorRowMajor<HalfFloatType>(tensor_row, 18, shape,
1152+
{f16_size * shape[1], f16_size});
1153+
1154+
const auto* buf_row = reinterpret_cast<const uint16_t*>(tensor_row->raw_data());
1155+
EXPECT_TRUE(util::Float16::FromBits(buf_row[0]).is_nan());
1156+
EXPECT_TRUE(util::Float16::FromBits(buf_row[9]).is_nan());
1157+
}
1158+
11191159
TEST_F(TestRecordBatch, ToTensorSupportedTypesMixed) {
11201160
const int length = 9;
11211161

cpp/src/arrow/table.cc

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "arrow/record_batch.h"
3737
#include "arrow/result.h"
3838
#include "arrow/status.h"
39+
#include "arrow/tensor.h"
3940
#include "arrow/type.h"
4041
#include "arrow/type_fwd.h"
4142
#include "arrow/type_traits.h"
@@ -346,6 +347,14 @@ Result<std::shared_ptr<Table>> Table::FromChunkedStructArray(
346347
array->length());
347348
}
348349

350+
Result<std::shared_ptr<Tensor>> Table::ToTensor(bool null_to_nan, bool row_major,
351+
MemoryPool* pool) const {
352+
std::shared_ptr<Tensor> tensor;
353+
ARROW_RETURN_NOT_OK(
354+
internal::TableToTensor(*this, null_to_nan, row_major, pool, &tensor));
355+
return tensor;
356+
}
357+
349358
std::vector<std::string> Table::ColumnNames() const {
350359
std::vector<std::string> names(num_columns());
351360
for (int i = 0; i < num_columns(); ++i) {

cpp/src/arrow/table.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,18 @@ class ARROW_EXPORT Table {
102102
static Result<std::shared_ptr<Table>> FromChunkedStructArray(
103103
const std::shared_ptr<ChunkedArray>& array);
104104

105+
/// \brief Convert Table to Tensor
106+
///
107+
/// Create a Tensor object.
108+
///
109+
/// \param[in] null_to_nan if true, convert nulls to NaN
110+
/// \param[in] row_major if true, create row-major Tensor else column-major Tensor
111+
/// \param[in] pool the memory pool to allocate the tensor buffer
112+
/// \return the resulting Tensor
113+
Result<std::shared_ptr<Tensor>> ToTensor(
114+
bool null_to_nan = false, bool row_major = true,
115+
MemoryPool* pool = default_memory_pool()) const;
116+
105117
/// \brief Return the table schema
106118
const std::shared_ptr<Schema>& schema() const { return schema_; }
107119

0 commit comments

Comments
 (0)