Convert float annotations if possible. (#1987)

* Convert float annotations if possible.

The client converts all arrays to floats.
If a category contains integer labels, and that category is copied, it will contains floats (e.g 1.0 instead of 1).
When that category is put back to the server, it fails in the tiledb code, which does not accept floats.
The solution is to convert a float category to integer, if possible.

  #1984

* updates
This commit is contained in:
bmccandless
2020-11-20 15:01:53 -08:00
committed by GitHub
parent f77038ad58
commit 2cc02a84cb
4 changed files with 52 additions and 6 deletions

View File

@@ -178,26 +178,26 @@ function promoteTypedArray(o) {
*/
if (isFpTypedArray(o) || Array.isArray(o)) return o;
let TyepdArrayCtor;
let TypedArrayCtor;
switch (o.constructor) {
case Int8Array:
case Uint8Array:
case Uint8ClampedArray:
case Int16Array:
case Uint16Array:
TyepdArrayCtor = Float32Array;
TypedArrayCtor = Float32Array;
break;
case Int32Array:
case Uint32Array:
TyepdArrayCtor = Float64Array;
TypedArrayCtor = Float64Array;
break;
default:
throw new Error("Unexpected data type returned from server.");
}
if (o.constructor === TyepdArrayCtor) return o;
return new TyepdArrayCtor(o);
if (o.constructor === TypedArrayCtor) return o;
return new TypedArrayCtor(o);
}
export function matrixFBSToDataframe(arrayBuffers) {

View File

@@ -156,7 +156,7 @@ def annotations_put_fbs_helper(data_adaptor, fbs):
new_label_df = decode_matrix_fbs(fbs)
if not new_label_df.empty:
data_adaptor.check_new_labels(new_label_df)
new_label_df = data_adaptor.check_new_labels(new_label_df)
annotations.write_labels(new_label_df, data_adaptor)

View File

@@ -249,6 +249,23 @@ class DataAdaptor(metaclass=ABCMeta):
if labels_df.shape[0] != shape[0]:
raise ValueError("Labels file must have same number of rows as data file.")
# This will convert a float column that contains integer data into an integer type.
# This case can occur when a user makes a copy of a category that originally contained integer data.
# The client always copies array data to floats, therefore the copy will contain floats instead of integers.
# float data is not allowed as a categorical type.
if any([np.issubdtype(coltype.type, np.floating) for coltype in labels_df.dtypes]):
labels_df = labels_df.convert_dtypes()
for col, dtype in zip(labels_df, labels_df.dtypes):
if isinstance(dtype, pd.Int32Dtype):
labels_df[col] = labels_df[col].astype("int32")
if isinstance(dtype, pd.Int64Dtype):
labels_df[col] = labels_df[col].astype("int64")
if any([np.issubdtype(coltype.type, np.floating) for coltype in labels_df.dtypes]):
raise ValueError("Columns may not have floating point types")
return labels_df
def data_frame_to_fbs_matrix(self, filter, axis):
"""
Retrieves data 'X' and returns in a flatbuffer Matrix.

View File

@@ -284,3 +284,32 @@ class WritableAnnotationTest(unittest.TestCase):
all_col_schema["cat_B"],
{"name": "cat_B", "type": "categorical", "categories": ["label_B"], "writable": True},
)
def test_put_float_data(self):
# verify that OBS PUTs (annotation_put_fbs) are accessible via
# GET (annotation_to_fbs_matrix)
n_rows = self.data.get_shape()[0]
# verifies that floating point with decimals fail.
fbs = make_fbs({"cat_F_FAIL": pd.Series([1.1] * n_rows, dtype=np.dtype("float"))})
with self.assertRaises(ValueError) as exception_context:
res = self.annotation_put_fbs(fbs)
self.assertEqual(str(exception_context.exception), "Columns may not have floating point types")
# verifies that floating point that can be converted to int passes
fbs = make_fbs({"cat_F_PASS": pd.Series([1.0] * n_rows, dtype="float")})
res = self.annotation_put_fbs(fbs)
self.assertEqual(res, json.dumps({"status": "OK"}))
# check read_labels
labels = self.annotations.read_labels(None)
fbsAll = self.data.annotation_to_fbs_matrix("obs", None, labels)
schema = schema_get_helper(self.data)
annotations = decode_fbs.decode_matrix_FBS(fbsAll)
self.assertEqual(annotations["n_rows"], n_rows)
all_col_schema = {c["name"]: c for c in schema["annotations"]["obs"]["columns"]}
self.assertEqual(
all_col_schema["cat_F_PASS"],
{"name": "cat_F_PASS", "type": "int32", "writable": True},
)