mirror of
https://github.com/apache/arrow.git
synced 2026-10-29 09:05:47 +08:00
Add Python unit tests, deduplicate for date and time types also when converting to Python objects
Change-Id: I0136ddb1498ac007509680ba9f9b3327e7e11a18
This commit is contained in:
1 parent
7a7873b87a
commit
ca88b96396
4 files changed
+128
-58
No files matched your search
@@ -447,13 +447,13 @@ inline Status ConvertAsPyObjects(const PandasOptions& options, const ChunkedArra
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template <typename Type>
|
||||
static Status ConvertIntegerObjects(const PandasOptions& options,
|
||||
const ChunkedArray& data, PyObject** out_values) {
|
||||
auto WrapValue = [](int64_t value, PyObject** out) {
|
||||
constexpr bool is_signed = std::is_signed<T>::value;
|
||||
*out = is_signed ? PyLong_FromLongLong(in_values[i])
|
||||
: PyLong_FromUnsignedLongLong(in_values[i]);
|
||||
using T = typename Type::c_type;
|
||||
auto WrapValue = [](T value, PyObject** out) {
|
||||
*out = std::is_signed<T>::value ? PyLong_FromLongLong(value)
|
||||
: PyLong_FromUnsignedLongLong(value);
|
||||
RETURN_IF_PYERROR();
|
||||
return Status::OK();
|
||||
};
|
||||
@@ -659,47 +659,34 @@ inline void ConvertDatetimeNanos(const ChunkedArray& data, int64_t* out_values)
|
||||
template <typename Type>
|
||||
static Status ConvertDates(const PandasOptions& options, const ChunkedArray& data,
|
||||
PyObject** out_values) {
|
||||
using ArrayType = typename TypeTraits<Type>::ArrayType;
|
||||
using T = typename
|
||||
|
||||
PyAcquireGIL lock;
|
||||
PyDateTime_IMPORT;
|
||||
|
||||
auto WrapValue = [](T value, PyObject** out) {
|
||||
return PyDate_from_int(value, Type::Unit, out);
|
||||
{
|
||||
PyAcquireGIL lock;
|
||||
PyDateTime_IMPORT;
|
||||
}
|
||||
auto WrapValue = [](typename Type::c_type value, PyObject** out) {
|
||||
RETURN_NOT_OK(PyDate_from_int(value, Type::UNIT, out));
|
||||
RETURN_IF_PYERROR();
|
||||
return Status::OK();
|
||||
};
|
||||
return ConvertAsPyObjects<Type>(options, data, WrapValue, out_values);
|
||||
}
|
||||
|
||||
template <typename TYPE>
|
||||
template <typename Type>
|
||||
static Status ConvertTimes(const PandasOptions& options, const ChunkedArray& data,
|
||||
PyObject** out_values) {
|
||||
using ArrayType = typename TypeTraits<TYPE>::ArrayType;
|
||||
|
||||
PyAcquireGIL lock;
|
||||
OwnedRef time_ref;
|
||||
|
||||
PyDateTime_IMPORT;
|
||||
|
||||
for (int c = 0; c < data.num_chunks(); c++) {
|
||||
const auto& arr = checked_cast<const ArrayType&>(*data.chunk(c));
|
||||
auto type = std::dynamic_pointer_cast<TYPE>(arr.type());
|
||||
DCHECK(type);
|
||||
|
||||
const TimeUnit::type unit = type->unit();
|
||||
|
||||
for (int64_t i = 0; i < arr.length(); ++i) {
|
||||
if (arr.IsNull(i)) {
|
||||
Py_INCREF(Py_None);
|
||||
*out_values++ = Py_None;
|
||||
} else {
|
||||
RETURN_NOT_OK(PyTime_from_int(arr.Value(i), unit, out_values++));
|
||||
RETURN_IF_PYERROR();
|
||||
}
|
||||
}
|
||||
{
|
||||
PyAcquireGIL lock;
|
||||
PyDateTime_IMPORT;
|
||||
}
|
||||
|
||||
return Status::OK();
|
||||
const TimeUnit::type unit = checked_cast<const Type&>(*data.type()).unit();
|
||||
|
||||
auto WrapValue = [unit](typename Type::c_type value, PyObject** out) {
|
||||
RETURN_NOT_OK(PyTime_from_int(value, unit, out));
|
||||
RETURN_IF_PYERROR();
|
||||
return Status::OK();
|
||||
};
|
||||
return ConvertAsPyObjects<Type>(options, data, WrapValue, out_values);
|
||||
}
|
||||
|
||||
static Status ConvertDecimals(const PandasOptions& options, const ChunkedArray& data,
|
||||
@@ -751,21 +738,21 @@ class ObjectBlock : public PandasBlock {
|
||||
if (type == Type::BOOL) {
|
||||
RETURN_NOT_OK(ConvertBooleanWithNulls(options_, data, out_buffer));
|
||||
} else if (type == Type::UINT8) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<uint8_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<UInt8Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::INT8) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<int8_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<Int8Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::UINT16) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<uint16_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<UInt16Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::INT16) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<int16_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<Int16Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::UINT32) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<uint32_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<UInt32Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::INT32) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<int32_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<Int32Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::UINT64) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<uint64_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<UInt64Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::INT64) {
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<int64_t>(options_, data, out_buffer));
|
||||
RETURN_NOT_OK(ConvertIntegerObjects<Int64Type>(options_, data, out_buffer));
|
||||
} else if (type == Type::BINARY) {
|
||||
RETURN_NOT_OK(ConvertBinaryLike<BinaryType>(options_, data, out_buffer));
|
||||
} else if (type == Type::STRING) {
|
||||
@@ -1776,9 +1763,7 @@ class ArrowDeserializer {
|
||||
|
||||
if (data_.null_count() > 0) {
|
||||
if (options_.integer_object_nulls) {
|
||||
using c_type = typename Type::c_type;
|
||||
|
||||
return VisitObjects(ConvertIntegerObjects<c_type>);
|
||||
return VisitObjects(ConvertIntegerObjects<Type>);
|
||||
} else {
|
||||
RETURN_NOT_OK(AllocateOutput(NPY_FLOAT64));
|
||||
auto out_values = reinterpret_cast<double*>(PyArray_DATA(arr_));
|
||||
|
||||
@@ -135,12 +135,11 @@ std::string FixedSizeBinaryType::ToString() const {
|
||||
// ----------------------------------------------------------------------
|
||||
// Date types
|
||||
|
||||
DateType::DateType(Type::type type_id, DateUnit unit)
|
||||
: FixedWidthType(type_id), unit_(unit) {}
|
||||
DateType::DateType(Type::type type_id) : FixedWidthType(type_id) {}
|
||||
|
||||
Date32Type::Date32Type() : DateType(Type::DATE32, DateUnit::DAY) {}
|
||||
Date32Type::Date32Type() : DateType(Type::DATE32) {}
|
||||
|
||||
Date64Type::Date64Type() : DateType(Type::DATE64, DateUnit::MILLI) {}
|
||||
Date64Type::Date64Type() : DateType(Type::DATE64) {}
|
||||
|
||||
std::string Date64Type::ToString() const { return std::string("date64[ms]"); }
|
||||
|
||||
|
||||
@@ -600,20 +600,19 @@ enum class DateUnit : char { DAY = 0, MILLI = 1 };
|
||||
/// \brief Base type class for date data
|
||||
class ARROW_EXPORT DateType : public FixedWidthType {
|
||||
public:
|
||||
DateUnit unit() const { return unit_; }
|
||||
virtual DateUnit unit() const = 0;
|
||||
|
||||
protected:
|
||||
DateType(Type::type type_id, DateUnit unit);
|
||||
DateUnit unit_;
|
||||
explicit DateType(Type::type type_id);
|
||||
};
|
||||
|
||||
/// Concrete type class for 32-bit date data (as number of days since UNIX epoch)
|
||||
class ARROW_EXPORT Date32Type : public DateType {
|
||||
public:
|
||||
static constexpr Type::type type_id = Type::DATE32;
|
||||
static constexpr DateUnit UNIT = DateUnit::DAY;
|
||||
|
||||
using c_type = int32_t;
|
||||
constexpr DateUnit Unit = DateUnit::DAY;
|
||||
|
||||
Date32Type();
|
||||
|
||||
@@ -623,15 +622,16 @@ class ARROW_EXPORT Date32Type : public DateType {
|
||||
std::string ToString() const override;
|
||||
|
||||
std::string name() const override { return "date32"; }
|
||||
DateUnit unit() const override { return UNIT; }
|
||||
};
|
||||
|
||||
/// Concrete type class for 64-bit date data (as number of milliseconds since UNIX epoch)
|
||||
class ARROW_EXPORT Date64Type : public DateType {
|
||||
public:
|
||||
static constexpr Type::type type_id = Type::DATE64;
|
||||
static constexpr DateUnit UNIT = DateUnit::MILLI;
|
||||
|
||||
using c_type = int64_t;
|
||||
constexpr DateUnit Unit = DateUnit::MILLI;
|
||||
|
||||
Date64Type();
|
||||
|
||||
@@ -641,6 +641,7 @@ class ARROW_EXPORT Date64Type : public DateType {
|
||||
std::string ToString() const override;
|
||||
|
||||
std::string name() const override { return "date64"; }
|
||||
DateUnit unit() const override { return UNIT; }
|
||||
};
|
||||
|
||||
struct TimeUnit {
|
||||
|
||||
@@ -2316,6 +2316,91 @@ def test_convert_unsupported_type_error_message():
|
||||
pa.Table.from_pandas(df)
|
||||
|
||||
|
||||
# ----------------------------------------------------------------------
|
||||
# Test object deduplication in to_pandas
|
||||
|
||||
|
||||
def _generate_dedup_example(nunique, repeats):
|
||||
unique_values = [tm.rands(10) for i in range(nunique)]
|
||||
return unique_values * repeats
|
||||
|
||||
|
||||
def _assert_nunique(obj, expected):
|
||||
assert len({id(x) for x in obj}) == expected
|
||||
|
||||
|
||||
def test_to_pandas_deduplicate_strings_array_types():
|
||||
nunique = 100
|
||||
repeats = 10
|
||||
values = _generate_dedup_example(nunique, repeats)
|
||||
|
||||
for arr in [pa.array(values, type=pa.binary()),
|
||||
pa.array(values, type=pa.utf8()),
|
||||
pa.chunked_array([values, values]),
|
||||
pa.column('foo', [values, values])]:
|
||||
_assert_nunique(arr.to_pandas(), nunique)
|
||||
_assert_nunique(arr.to_pandas(deduplicate_objects=False), len(arr))
|
||||
|
||||
|
||||
def test_to_pandas_deduplicate_strings_table_types():
|
||||
nunique = 100
|
||||
repeats = 10
|
||||
values = _generate_dedup_example(nunique, repeats)
|
||||
|
||||
arr = pa.array(values)
|
||||
rb = pa.RecordBatch.from_arrays([arr], ['foo'])
|
||||
tbl = pa.Table.from_batches([rb])
|
||||
|
||||
for obj in [rb, tbl]:
|
||||
_assert_nunique(obj.to_pandas()['foo'], nunique)
|
||||
_assert_nunique(obj.to_pandas(deduplicate_objects=False)['foo'],
|
||||
len(obj))
|
||||
|
||||
|
||||
def test_to_pandas_deduplicate_integers_as_objects():
|
||||
nunique = 100
|
||||
repeats = 10
|
||||
|
||||
# Python automatically interns smaller integers
|
||||
unique_values = list(np.random.randint(10000000, 1000000000, size=nunique))
|
||||
unique_values[nunique // 2] = None
|
||||
|
||||
arr = pa.array(unique_values * repeats)
|
||||
|
||||
_assert_nunique(arr.to_pandas(integer_object_nulls=True), nunique)
|
||||
_assert_nunique(arr.to_pandas(integer_object_nulls=True,
|
||||
deduplicate_objects=False),
|
||||
# Account for None
|
||||
(nunique - 1) * repeats + 1)
|
||||
|
||||
|
||||
def test_to_pandas_deduplicate_date_time():
|
||||
nunique = 100
|
||||
repeats = 10
|
||||
|
||||
unique_values = list(range(nunique))
|
||||
|
||||
cases = [
|
||||
# raw type, array type, to_pandas options
|
||||
('int32', 'date32', {'date_as_object': True}),
|
||||
('int64', 'date64', {'date_as_object': True}),
|
||||
('int32', 'time32[ms]', {}),
|
||||
('int64', 'time64[us]', {})
|
||||
]
|
||||
|
||||
for raw_type, array_type, pandas_options in cases:
|
||||
raw_arr = pa.array(unique_values * repeats, type=raw_type)
|
||||
casted_arr = raw_arr.cast(array_type)
|
||||
|
||||
_assert_nunique(casted_arr.to_pandas(**pandas_options),
|
||||
nunique)
|
||||
_assert_nunique(casted_arr.to_pandas(deduplicate_objects=False,
|
||||
**pandas_options),
|
||||
len(casted_arr))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
def test_table_from_pandas_keeps_column_order_of_dataframe():
|
||||
df1 = pd.DataFrame(OrderedDict([
|
||||
('partition', [0, 0, 1, 1]),
|
||||
|
||||
Reference in new issue
Block a user