Add Python unit tests, deduplicate for date and time types also when converting to Python objects

Change-Id: I0136ddb1498ac007509680ba9f9b3327e7e11a18
This commit is contained in:
Wes McKinney committed 2018-12-26 13:52:01 -06:00
1 parent 7a7873b87a
commit ca88b96396
4 files changed
+128 -58

No files matched your search

+34 -49
View File
@@ -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_));
+3 -4
View File
@@ -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]"); }
+6 -5
View File
@@ -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]),